diff --git a/Assets/White-OpenNoteLogo.svg b/Assets/White-OpenNoteLogo.svg
new file mode 100644
index 0000000..591dcf6
--- /dev/null
+++ b/Assets/White-OpenNoteLogo.svg
@@ -0,0 +1,14 @@
+
+
+
diff --git a/Assets/icons/svg_add_page.svg b/Assets/icons/svg_add_page.svg
new file mode 100644
index 0000000..548f706
--- /dev/null
+++ b/Assets/icons/svg_add_page.svg
@@ -0,0 +1,11 @@
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_align_center.svg b/Assets/icons/svg_align_center.svg
new file mode 100644
index 0000000..92d15d7
--- /dev/null
+++ b/Assets/icons/svg_align_center.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_align_left.svg b/Assets/icons/svg_align_left.svg
new file mode 100644
index 0000000..a04bce0
--- /dev/null
+++ b/Assets/icons/svg_align_left.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_align_right.svg b/Assets/icons/svg_align_right.svg
new file mode 100644
index 0000000..79600b9
--- /dev/null
+++ b/Assets/icons/svg_align_right.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_arrow.svg b/Assets/icons/svg_arrow.svg
new file mode 100644
index 0000000..9d990ed
--- /dev/null
+++ b/Assets/icons/svg_arrow.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_bulletUA.svg b/Assets/icons/svg_bulletUA.svg
new file mode 100644
index 0000000..74a1694
--- /dev/null
+++ b/Assets/icons/svg_bulletUA.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_bulletUR.svg b/Assets/icons/svg_bulletUR.svg
new file mode 100644
index 0000000..7aee439
--- /dev/null
+++ b/Assets/icons/svg_bulletUR.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_bullet_number.svg b/Assets/icons/svg_bullet_number.svg
new file mode 100644
index 0000000..ab2ddca
--- /dev/null
+++ b/Assets/icons/svg_bullet_number.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_bullets.svg b/Assets/icons/svg_bullets.svg
new file mode 100644
index 0000000..fb527c0
--- /dev/null
+++ b/Assets/icons/svg_bullets.svg
@@ -0,0 +1,19 @@
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_close.svg b/Assets/icons/svg_close.svg
new file mode 100644
index 0000000..e25249d
--- /dev/null
+++ b/Assets/icons/svg_close.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_copy.svg b/Assets/icons/svg_copy.svg
new file mode 100644
index 0000000..4bd7a9a
--- /dev/null
+++ b/Assets/icons/svg_copy.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_crop.svg b/Assets/icons/svg_crop.svg
new file mode 100644
index 0000000..5c0d05f
--- /dev/null
+++ b/Assets/icons/svg_crop.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_cut.svg b/Assets/icons/svg_cut.svg
new file mode 100644
index 0000000..f915297
--- /dev/null
+++ b/Assets/icons/svg_cut.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_date.svg b/Assets/icons/svg_date.svg
new file mode 100644
index 0000000..ec709e1
--- /dev/null
+++ b/Assets/icons/svg_date.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_dateTime.svg b/Assets/icons/svg_dateTime.svg
new file mode 100644
index 0000000..8c47144
--- /dev/null
+++ b/Assets/icons/svg_dateTime.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_delete.svg b/Assets/icons/svg_delete.svg
new file mode 100644
index 0000000..dec5ed3
--- /dev/null
+++ b/Assets/icons/svg_delete.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_expand.svg b/Assets/icons/svg_expand.svg
new file mode 100644
index 0000000..7084297
--- /dev/null
+++ b/Assets/icons/svg_expand.svg
@@ -0,0 +1,28 @@
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_flip_horizontal.svg b/Assets/icons/svg_flip_horizontal.svg
new file mode 100644
index 0000000..f58662e
--- /dev/null
+++ b/Assets/icons/svg_flip_horizontal.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_flip_vertical.svg b/Assets/icons/svg_flip_vertical.svg
new file mode 100644
index 0000000..f43b4d0
--- /dev/null
+++ b/Assets/icons/svg_flip_vertical.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_fullscreen.svg b/Assets/icons/svg_fullscreen.svg
new file mode 100644
index 0000000..3a39a6c
--- /dev/null
+++ b/Assets/icons/svg_fullscreen.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_hyperlink.svg b/Assets/icons/svg_hyperlink.svg
new file mode 100644
index 0000000..930588a
--- /dev/null
+++ b/Assets/icons/svg_hyperlink.svg
@@ -0,0 +1,11 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_insert_space.svg b/Assets/icons/svg_insert_space.svg
new file mode 100644
index 0000000..455ddfe
--- /dev/null
+++ b/Assets/icons/svg_insert_space.svg
@@ -0,0 +1,17 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_paper.svg b/Assets/icons/svg_paper.svg
new file mode 100644
index 0000000..3a7d0d5
--- /dev/null
+++ b/Assets/icons/svg_paper.svg
@@ -0,0 +1,6 @@
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_paste.svg b/Assets/icons/svg_paste.svg
new file mode 100644
index 0000000..9376c18
--- /dev/null
+++ b/Assets/icons/svg_paste.svg
@@ -0,0 +1,13 @@
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_pictures.svg b/Assets/icons/svg_pictures.svg
new file mode 100644
index 0000000..c51204f
--- /dev/null
+++ b/Assets/icons/svg_pictures.svg
@@ -0,0 +1,6 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_question.svg b/Assets/icons/svg_question.svg
new file mode 100644
index 0000000..f0e4806
--- /dev/null
+++ b/Assets/icons/svg_question.svg
@@ -0,0 +1,26 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_redo.svg b/Assets/icons/svg_redo.svg
new file mode 100644
index 0000000..a96d7e5
--- /dev/null
+++ b/Assets/icons/svg_redo.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_rename.svg b/Assets/icons/svg_rename.svg
new file mode 100644
index 0000000..f64df38
--- /dev/null
+++ b/Assets/icons/svg_rename.svg
@@ -0,0 +1,2 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_rotate_left.svg b/Assets/icons/svg_rotate_left.svg
new file mode 100644
index 0000000..1999b02
--- /dev/null
+++ b/Assets/icons/svg_rotate_left.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_rotate_right.svg b/Assets/icons/svg_rotate_right.svg
new file mode 100644
index 0000000..f87f18a
--- /dev/null
+++ b/Assets/icons/svg_rotate_right.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_screensnip.svg b/Assets/icons/svg_screensnip.svg
new file mode 100644
index 0000000..4584889
--- /dev/null
+++ b/Assets/icons/svg_screensnip.svg
@@ -0,0 +1,8 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_search.svg b/Assets/icons/svg_search.svg
new file mode 100644
index 0000000..a19fb2a
--- /dev/null
+++ b/Assets/icons/svg_search.svg
@@ -0,0 +1 @@
+
\ No newline at end of file
diff --git a/Assets/icons/svg_shrink.svg b/Assets/icons/svg_shrink.svg
new file mode 100644
index 0000000..ed75d69
--- /dev/null
+++ b/Assets/icons/svg_shrink.svg
@@ -0,0 +1,28 @@
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_strikethrough.svg b/Assets/icons/svg_strikethrough.svg
new file mode 100644
index 0000000..7da4dad
--- /dev/null
+++ b/Assets/icons/svg_strikethrough.svg
@@ -0,0 +1,6 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_table.svg b/Assets/icons/svg_table.svg
new file mode 100644
index 0000000..9c21ff1
--- /dev/null
+++ b/Assets/icons/svg_table.svg
@@ -0,0 +1,4 @@
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_textHighlightColor.svg b/Assets/icons/svg_textHighlightColor.svg
new file mode 100644
index 0000000..1cc087a
--- /dev/null
+++ b/Assets/icons/svg_textHighlightColor.svg
@@ -0,0 +1,14 @@
+
+
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_time.svg b/Assets/icons/svg_time.svg
new file mode 100644
index 0000000..bfd356a
--- /dev/null
+++ b/Assets/icons/svg_time.svg
@@ -0,0 +1,15 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_tray.svg b/Assets/icons/svg_tray.svg
new file mode 100644
index 0000000..d4b9c8f
--- /dev/null
+++ b/Assets/icons/svg_tray.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_undo.svg b/Assets/icons/svg_undo.svg
new file mode 100644
index 0000000..d463200
--- /dev/null
+++ b/Assets/icons/svg_undo.svg
@@ -0,0 +1,7 @@
+
+
+
\ No newline at end of file
diff --git a/Assets/icons/svg_windowed.svg b/Assets/icons/svg_windowed.svg
new file mode 100644
index 0000000..6bde8f2
--- /dev/null
+++ b/Assets/icons/svg_windowed.svg
@@ -0,0 +1 @@
+
diff --git a/Models/DraggableContainer.py b/Models/DraggableContainer.py
index 57b248d..5a6202f 100644
--- a/Models/DraggableContainer.py
+++ b/Models/DraggableContainer.py
@@ -1,5 +1,6 @@
from PySide6.QtCore import *
from PySide6.QtGui import *
+from PySide6.QtGui import QKeyEvent
from PySide6.QtWidgets import *
from Modules.Enums import *
from Modules.EditorSignals import editorSignalsInstance, ChangedWidgetAttribute
@@ -24,9 +25,10 @@ class DraggableContainer(QWidget):
menu = None
mode = Mode.NONE
position = None
- outFocus = Signal(bool)
- newGeometry = Signal(QRect)
+ outFocus = Signal(bool) # Signal emitted when the container loses focus
+ newGeometry = Signal(QRect) # Signal emitted when the container's geometry changes
+ # Initializes a DraggableContainer object
def __init__(self, childWidget, editorFrame):
super().__init__(parent=editorFrame)
@@ -52,8 +54,11 @@ def __init__(self, childWidget, editorFrame):
self.menu = self.buildDragContainerMenu()
if hasattr(self.childWidget, "newGeometryEvent"): self.newGeometry.connect(childWidget.newGeometryEvent)
- editorSignalsInstance.widgetAttributeChanged.connect(self.widgetAttributeChanged)
+
+ editorSignalsInstance.widgetAttributeChanged.connect(self.deselect_and_delete)
+
+
def setChildWidget(self, childWidget):
if childWidget:
self.childWidget = childWidget
@@ -64,23 +69,24 @@ def setChildWidget(self, childWidget):
self.vLayout.addWidget(childWidget)
self.vLayout.setContentsMargins(0,0,0,0)
- def eventFilter(self, obj, event):
+ def eventFilter(self, obj, e):
- # If child widget resized itsself, resize this drag container, not ideal bc child resizes on hover
- if isinstance(event, QResizeEvent):
+ # If child widget resized itself, resize this drag container, not ideal bc child resizes on hover
+ if isinstance(e, QResizeEvent):
self.resize(self.childWidget.size())
return False
+ # Displays the container menu at a given position
def popupShow(self, pt: QPoint):
global_ = self.mapToGlobal(pt)
self.m_showMenu = True
self.menu.exec(global_)
self.m_showMenu = False
-
+
def mousePressEvent(self, e: QMouseEvent):
self.position = QPoint(e.globalX() - self.geometry().x(), e.globalY() - self.geometry().y())
- print("DC MOUSE PRESS")
+ print("Draggable Container MOUSE PRESS")
# Undo related
# self.old_x = e.globalX()
@@ -93,15 +99,36 @@ def mousePressEvent(self, e: QMouseEvent):
print("NOT EDIT")
return
if not e.buttons() and Qt.LeftButton:
- print("DC GOT MOUSE PRESS")
+ print("Draggable Container GOT MOUSE PRESS")
self.setCursorShape(e.pos())
+
+
return True
if e.button() == Qt.RightButton:
self.popupShow(e.pos())
e.accept()
+ self.childWidget.setAttribute(Qt.WA_TransparentForMouseEvents, False)
+ self.childWidget.setFocus()
+ # checks if the child is a textbox
+ if (isinstance(self.childWidget, QTextEdit)):
+ # sets cursor position to where mouse is upon clicking
+ self.childWidget.setCursorPosition(e)
+ # check settings at cursor and match the visual of the toolbar toggle with the current cursor setting
+
+
+ #brings text cursor to cursor position but causes exception
+ '''if isinstance(self.childWidget, TextboxWidget):
+ self.childWidget.setCursorPosition(e)
+ else:
+ # Handle the case where self.childWidget is not a TextBox
+ pass'''
+
+
+
# On double click, focus on child and make mouse events pass through this container to child
def mouseDoubleClickEvent(self, e: QMouseEvent):
+ print("MOUSEDOUBLECLICKEVENT FROM DRAGGABLE CONTAINER")
self.childWidget.setAttribute(Qt.WA_TransparentForMouseEvents, False)
self.childWidget.setFocus()
return
@@ -114,18 +141,21 @@ def mouseReleaseEvent(self, e: QMouseEvent):
return True # Dont let the release go to the editor frame
def leaveEvent(self, e: QMouseEvent):
+ #if(self.childWidget.hasFocus() == False):
self.childWidget.setAttribute(Qt.WA_TransparentForMouseEvents, True)
self.setStyleSheet("border: none;")
+ # If mouse leaves draggable container, set focus to the editor
+ #if self.childWidget.hasFocus():
+ # self.setFocus()'''
- # Delete this DC if childWidget says it's empty
- if hasattr(self.childWidget, "checkEmpty"):
- if self.childWidget.checkEmpty():
- editorSignalsInstance.widgetRemoved.emit(self)
-
- # ???
- if self.childWidget.hasFocus():
- self.setFocus()
+ # this is to lose the border of the draggable container if the container is no longer in focus
+ '''def focusOutEvent(self, event: QMouseEvent):
+ print("FOCUS OUT EVENT CALLED FROM DRAGGABLE CONTAINER")
+ self.childWidget.setAttribute(Qt.WA_TransparentForMouseEvents, True)
+ self.setStyleSheet("border: none;")
+ super().focusOutEvent(event)'''
+ # Builds and returns the context menu for the container
def buildDragContainerMenu(self):
# Get custom menu actions from child widget
@@ -142,24 +172,113 @@ def buildDragContainerMenu(self):
menu.addAction(item)
# Add standard menu actions
- delete = QAction("Delete", self)
- delete.triggered.connect(lambda: editorSignalsInstance.widgetRemoved.emit(self))
- menu.addAction(delete)
+ cut = QAction("Cu&t", self)
+ cut.triggered.connect(lambda: editorSignalsInstance.widgetCut.emit(self))
- copy = QAction("Copy", self)
+ copy = QAction("&Copy", self)
copy.triggered.connect(lambda: editorSignalsInstance.widgetCopied.emit(self))
- menu.addAction(copy)
-
- cut = QAction("Cut", self)
- cut.triggered.connect(lambda: editorSignalsInstance.widgetCut.emit(self))
- menu.addAction(cut)
+
+ paste = QAction("&Paste", self)
+ paste.triggered.connect(lambda: self.pasteWidget(self.pos()))
+
+ delete = QAction("&Delete", self)
+ delete.triggered.connect(lambda: editorSignalsInstance.widgetRemoved.emit(self))
+
+ menu.addActions([cut, copy, paste, delete])
+
+ menu.addSeparator()
+
+ link = QAction("&Link", self)
+ # link.triggered.connect(lambda: self.insertLink(event.pos()))
+
+ menu.addAction(link)
+
+ menu.addSeparator()
+
+ orderMenu = QMenu("&Order", self)
+
+ bringForwards = QAction("Bring &Forwards", self)
+ bringForwards.triggered.connect(self.childWidget.raise_())
+
+ orderMenu.addAction(bringForwards)
+
+ # not ready for deployment
+ # menu.addMenu(orderMenu)
+
+ # text boxes
+ if isinstance(self.childWidget, QTextEdit):
+ table = QAction("&Table", self)
+ # table.triggered.connect(lambda: self.newWidgetOnSection(TableWidget, event.pos()))
+
+ menu.addAction(table)
+
+ menu.addSeparator()
+
+ # images
+ elif isinstance(self.childWidget, QLabel):
+ # Create submenus for rotate and resize
+ rotateMenu = QMenu("R&otate", self)
+ rotateMenu.setStyleSheet("font-size: 11pt;")
+
+ resizeMenu = QMenu("&Resize", self)
+ resizeMenu.setStyleSheet("font-size: 11pt;")
+
+ # Create actions for rotate submenu
+ rotateRightAction = QAction("Rotate &Right 90°", self)
+ rotateRightAction.triggered.connect(self.childWidget.rotate90Right)
+ rotateRightAction.setIcon(QIcon('./Assets/icons/svg_rotate_right'))
+
+ rotateLeftAction = QAction("Rotate &Left 90°", self)
+ rotateLeftAction.triggered.connect(self.childWidget.rotate90Left)
+ rotateLeftAction.setIcon(QIcon('./Assets/icons/svg_rotate_left'))
+
+ flipHorizontal = QAction("Flip &Horizontal", self)
+ flipHorizontal.triggered.connect(self.childWidget.flipHorizontal)
+ flipHorizontal.setIcon(QIcon('./Assets/icons/svg_flip_horizontal'))
+
+ flipVertical = QAction("Flip &Vertical", self)
+ flipVertical.triggered.connect(self.childWidget.flipVertical)
+ flipVertical.setIcon(QIcon('./Assets/icons/svg_flip_vertical'))
+
+ # Add actions to rotate submenu
+ rotateMenu.addActions([rotateLeftAction, rotateRightAction, flipHorizontal, flipVertical])
+
+ # Create actions for resize submenu
+ shrinkImageAction = QAction("&Shrink Image", self)
+ shrinkImageAction.triggered.connect(self.childWidget.shrinkImage)
+ shrinkImageAction.setIcon(QIcon('./Assets/icons/svg_shrink'))
+
+ expandImageAction = QAction("&Expand Image", self)
+ expandImageAction.triggered.connect(self.childWidget.expandImage)
+ expandImageAction.setIcon(QIcon('./Assets/icons/svg_expand'))
+
+ # Add actions to resize submenu
+ resizeMenu.addActions([shrinkImageAction, expandImageAction])
+
+ # Add submenus to the main menu
+ menu.addMenu(rotateMenu)
+ menu.addMenu(resizeMenu)
+
+ # tables
+ elif isinstance(self.childWidget, QWidget):
+ tableMenu = QMenu("&Table", self)
+ tableMenu.setStyleSheet("font-size: 11pt;")
+
+ addRow = QAction("Add Row", self)
+ addRow.triggered.connect(self.childWidget.addRow)
+
+ addCol = QAction("Add Column", self)
+ addCol.triggered.connect(self.childWidget.addCol)
+
+ tableMenu.addActions([addRow, addCol])
+ menu.addMenu(tableMenu)
# Add any non-widget type menu actions from child
for item in customMenuItems:
if type(item) != QWidgetAction:
menu.addAction(item)
- return menu
+ return menu # returns the QMenu: The constructed context menu
# Determine which cursor to show and which mode to be in when user is interacting with the box
def setCursorShape(self, e_pos: QPoint):
@@ -217,7 +336,7 @@ def setCursorShape(self, e_pos: QPoint):
self.setCursor(QCursor(Qt.SizeVerCursor))
self.mode = Mode.RESIZEB
else:
- self.setCursor(QCursor(Qt. ArrowCursor))
+ self.setCursor(QCursor(Qt.ArrowCursor))
self.mode = Mode.MOVE
# Determine how to handle the mouse being moved inside the box
@@ -246,6 +365,7 @@ def mouseMoveEvent(self, e: QMouseEvent):
# debt: To make images resize better, ImageWidget should probaly implement this and setCursorShape
# So that it can make the cursor move with the corners of pixmap and not corners of this container
if (self.mode != Mode.MOVE) and e.buttons() and Qt.LeftButton:
+ child_widget = self.childWidget
if self.mode == Mode.RESIZETL: # Left - Top
newwidth = e.globalX() - self.position.x() - self.geometry().x()
newheight = e.globalY() - self.position.y() - self.geometry().y()
@@ -257,6 +377,8 @@ def mouseMoveEvent(self, e: QMouseEvent):
toMove = e.globalPos() - self.position
self.resize(e.x(), self.geometry().height() - newheight)
self.move(self.x(), toMove.y())
+ '''if hasattr(self.childWidget, "newGeometryEvent"):
+ self.newGeometry.connect(self.childWidget.newGeometryEvent)'''
elif self.mode== Mode.RESIZEBL: # Left - Bottom
newwidth = e.globalX() - self.position.x() - self.geometry().x()
toMove = e.globalPos() - self.position
@@ -277,33 +399,59 @@ def mouseMoveEvent(self, e: QMouseEvent):
elif self.mode == Mode.RESIZER: # Right
self.resize(e.x(), self.height())
elif self.mode == Mode.RESIZEBR:# Right - Bottom
- self.resize(e.x(), e.y())
+ #if child is a image, resize differently
+ if isinstance(child_widget, QLabel):
+ # change this
+ self.resize(e.x(), e.y())
+ else:
+ self.resize(e.x(), e.y())
self.parentWidget().repaint()
self.newGeometry.emit(self.geometry())
+
+ # Used for deselecting text and removing container if empty
+ def deselect_and_delete(self, changedWidgetAttribute):
+ #print(f"changedWidgetAttribute is {changedWidgetAttribute} and value is {value}")
+ child_widget = self.childWidget
+
+ # If clicking on no object, deselect text
+ if hasattr(child_widget, "deselectText") and (changedWidgetAttribute == ChangedWidgetAttribute.LoseFocus):
+ child_widget.deselectText()
+ # remove the widget if empty
+ if hasattr(self.childWidget, "checkEmpty") and isinstance(child_widget, QTextEdit):
+ if self.childWidget.checkEmpty():
+ print("Removing empty container")
+ editorSignalsInstance.widgetRemoved.emit(self)
+
+ def connectTableSignals(self, tableWidget):
+ tableWidget.rowAdded.connect(self.resizeTable)
+ def resizeTable(self):
+ self.resize(self.childWidget.size())
+ self.newGeometry.emit(self.geometry())
+ self.parentWidget().repaint()
- # Pass the event to the child widget if this container is focuesd, and childwidget implements the method to receive it
- def widgetAttributeChanged(self, changedWidgetAttribute, value):
-
- cw = self.childWidget
-
- if hasattr(cw, "changeFontSizeEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.FontSize):
- cw.changeFontSizeEvent(value)
-
- if hasattr(cw, "changeFontBoldEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.FontBold):
- cw.changeFontBoldEvent()
-
- if hasattr(cw, "changeFontItalicEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.FontItalic):
- cw.changeFontItalicEvent()
-
- if hasattr(cw, "changeFontUnderlineEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.FontUnderline):
- cw.changeFontUnderlineEvent()
+ def keyPressEvent(self, event: QKeyEvent) -> None:
+ if event.key() == Qt.Key_Shift:
+ print("SHIFT PRESSED")
+ self.shift_pressed = True
+ else:
+ super().keyPressEvent(event)
+ def keyReleaseEvent(self, event: QKeyEvent) -> None:
+ if event.key() == Qt.Key_Shift:
+ print("SHIFT RELEASED")
+ self.shift_pressed = False
+ else:
+ super().keyReleaseEvent(event)
- if hasattr(cw, "changeFontEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.Font):
- cw.changeFontEvent(value)
+ # Pastes a widget at the specified position
+ def pasteWidget(self, clickPos):
+ widgetOnClipboard = self.clipboard.getWidgetToPaste()
- if hasattr(cw, "changeFontColorEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.FontColor):
- cw.changeFontColorEvent(value)
+ dc = DraggableContainer(widgetOnClipboard, self) # Create a new DraggableContainer with the widget to paste
+ self.undoHandler.pushCreate(dc)
+ editorSignalsInstance.widgetAdded.emit(dc) # Notify section that widget was added
+ editorSignalsInstance.changeMade.emit()
- if hasattr(cw, "changeBackgroundColorEvent") and (changedWidgetAttribute == ChangedWidgetAttribute.BackgroundColor):
- cw.changeBackgroundColorEvent(value)
+ # Move the container to the specified position and show it
+ dc.move(clickPos.x(), clickPos.y())
+ dc.show()
diff --git a/Models/Editor.py b/Models/Editor.py
index 89101c8..721b7c9 100644
--- a/Models/Editor.py
+++ b/Models/Editor.py
@@ -13,16 +13,19 @@
from Views.EditorFrameView import EditorFrameView
from Views.NotebookTitleView import NotebookTitleView
from Views.SectionView import SectionView
+from Modules.Titlebar import Build_titlebar
class Editor(QMainWindow):
def __init__(self):
super().__init__()
- # self.setWindowFlag(Qt.FramelessWindowHint)
+ self.setWindowFlags(Qt.WindowType.FramelessWindowHint)
+ # self.setWindowFlags(Qt.WindowType.Window | Qt.WindowType.CustomizeWindowHint) # This changes the window back to a state that allows resizing
self.notebook = NotebookModel('Untitled Notebook') # Current notebook object
# self.selected = None # Selected object (for font attributes of TextBox)
# View-Controllers that let the user interact with the underlying models
+ self.titlebar = Build_titlebar(self)
self.notebookTitleView = NotebookTitleView(self.notebook.title)
self.frameView = EditorFrameView(self)
self.pageView = PageView(self.notebook.pages)
@@ -30,8 +33,105 @@ def __init__(self):
self.autosaver = Autosaver(self) # Waits for change signals and saves the notebook
self.setFocus()
+ self.undoStack = []
+
+ self.settings = QSettings("UNT - Team Olive", "OpenNote") #pre-saved settings needed for window state restoration
build_ui(self)
+
+ action_names = self.save_toolbar_actions([self.fileToolbar, self.homeToolbar, self.insertToolbar, self.drawToolbar, self.pluginToolbar])
+ self.titlebar.set_action_names(action_names)
+
+ def closeEvent(self, event):
+
+ print("Window closing event triggered")
+
+ self.settings.setValue("geometry", self.saveGeometry())
+ self.settings.setValue("windowState", self.saveState())
+ super().closeEvent(event)
+
+ # Handles the window showing event, it restores the window's geometry and state
+ def showEvent(self, event):
+ print("Window showing event triggered")
+
+ self.restoreGeometry(self.settings.value("geometry", self.saveGeometry()))
+ self.restoreState(self.settings.value("windowState", self.saveState()))
+ super().showEvent(event)
+
# def focusInEvent(self, event):
# self.repaint()
+ # Handles changes in window state, particularly the window state change event
+ def changeEvent(self, event):
+ if event.type() == QEvent.Type.WindowStateChange:
+ self.titlebar.window_state_changed(self.windowState())
+ super().changeEvent(event)
+ event.accept()
+
+ def triggerUndo(self):
+ print("Item added to Undo Stack")
+ focused_widget = self.focusWidget()
+
+
+ if focused_widget and isinstance(focused_widget, (QTextEdit, QLineEdit)):
+ cursor = focused_widget.textCursor() # Get the cursor of the widget
+ if cursor.position() > 0: # Check if there's a character to delete
+ cursor.movePosition(QTextCursor.Left, QTextCursor.KeepAnchor) # Select character to the left
+ char_to_delete = cursor.selectedText() # Get the selected text
+ self.undoStack.append(char_to_delete) # Append it to the stack
+ print(f"Stored '{char_to_delete}' in redo stack")
+
+
+ backspace_event = QKeyEvent(QEvent.KeyPress, Qt.Key_Backspace, Qt.NoModifier)
+ backspace_release_event = QKeyEvent(QEvent.KeyRelease, Qt.Key_Backspace, Qt.NoModifier)
+
+ QApplication.postEvent(focused_widget, backspace_event)
+ QApplication.postEvent(focused_widget, backspace_release_event)
+
+ def triggerRedo(self):
+ if not self.undoStack:
+ print("No characters to redo")
+ return
+
+ char_to_redo = self.undoStack.pop() # Get the last character from the stack
+ focused_widget = self.focusWidget()
+
+ if focused_widget and isinstance(focused_widget, (QTextEdit, QLineEdit)):
+ redo_event = QKeyEvent(QEvent.KeyPress, 0, Qt.NoModifier, text=char_to_redo)
+ QApplication.postEvent(focused_widget, redo_event)
+ print(f"Redo action: Typed '{char_to_redo}'")
+ print(f"Item Removed from stack")
+ else:
+ print("Focused widget is not a QTextEdit or QLineEdit")
+
+ # mousePress, mouseMove, and mouseRelease handle mouse move events inside the window
+
+ def mousePressEvent(self, event):
+ if event.button() == Qt.LeftButton:
+ self.moveFlag = True
+ self.movePosition = event.globalPos() - self.pos()
+ event.accept()
+
+ def mouseMoveEvent(self, event):
+ if Qt.LeftButton and self.moveFlag:
+ self.setWindowState(Qt.WindowNoState)
+ self.move(event.globalPos() - self.movePosition)
+ event.accept()
+
+ def mouseReleaseEvent(self, QMouseEvent):
+ self.moveFlag = False
+
+ # Collects action names from the toolbars
+ def save_toolbar_actions(self, toolbars):
+ action_names = []
+ for toolbar in toolbars: #loops through all toolbars
+ for action in toolbar.actions():
+ if action.objectName():
+ action_names.append(action.objectName()) # Add the object name of the action to the list
+
+ # Loop through child widgets of the toolbar (buttons, tool buttons, combo boxes)
+ for widget in toolbar.findChildren(QPushButton) + toolbar.findChildren(QToolButton) + toolbar.findChildren(QComboBox):
+ if widget.objectName() and widget.objectName() != "qt_toolbar_ext_button":
+ action_names.append(widget.objectName()) # Add the object name of the widget to the list
+
+ return action_names # Return the list of action names collected from the toolbars
diff --git a/Models/NotebookModel.py b/Models/NotebookModel.py
index 6ecf21e..c92a71e 100644
--- a/Models/NotebookModel.py
+++ b/Models/NotebookModel.py
@@ -1,5 +1,8 @@
+
+# Represents a notebook in the application.
class NotebookModel:
def __init__(self, title):
- self.path = None
- self.title = title
- self.pages = [] # PageModel[]
+ self.path = None # The file path of the notebook if saved
+ self.title = title # The title of the notebook
+
+ self.pages = [] # PageModel[] - List to store PageModel instances representing pages in the notebook
diff --git a/Models/PageModel.py b/Models/PageModel.py
index 2643010..77ca4e3 100644
--- a/Models/PageModel.py
+++ b/Models/PageModel.py
@@ -1,9 +1,11 @@
import uuid
+# Represents a page within a notebook
+
class PageModel:
def __init__(self, title: str, parentUuid: int = 0):
self.title = title
- self.sections = [] # SectionModel[]
+ self.sections = [] # SectionModel[] - List to store SectionModel instances representing sections in the page
# These are used to represent the tree structure, the model is not actually concerned with parent and children
# The tree structure lets us build a view that we can interact with as if there were really nested pages
@@ -12,9 +14,9 @@ def __init__(self, title: str, parentUuid: int = 0):
@staticmethod
def newRootPage():
- rootPage = PageModel("Notebook Pages")
+ rootPage = PageModel("Notebook")
rootPage.__uuid = 0
- return rootPage
+ return rootPage # A new root page
def isRoot(self):
return self.__uuid == 0
diff --git a/Models/SectionModel.py b/Models/SectionModel.py
index 522d404..f9c9df9 100644
--- a/Models/SectionModel.py
+++ b/Models/SectionModel.py
@@ -6,7 +6,7 @@
class SectionModel:
def __init__(self, title: str):
self.title = title
- self.widgets: List[DraggableContainer] = []
+ self.widgets: List[DraggableContainer] = [] # List of draggable containers in the section, each container holds an instance of a widget (ex. TextboxWidget)
# When saving, convert the list of DraggableContainers to a list of their child widget's models
def __getstate__(self):
diff --git a/Modules/BuildUI.py b/Modules/BuildUI.py
index 6b105a4..11c4ff2 100644
--- a/Modules/BuildUI.py
+++ b/Modules/BuildUI.py
@@ -3,120 +3,445 @@
from PySide6.QtCore import *
from PySide6.QtGui import *
+from PySide6.QtSvg import *
from PySide6.QtWidgets import *
-from Modules.EditorSignals import editorSignalsInstance, ChangedWidgetAttribute
+from Models.DraggableContainer import DraggableContainer
+from Widgets.Textbox import *
+
+from Modules.EditorSignals import editorSignalsInstance, ChangedWidgetAttribute, CheckSignal
+from Modules.Undo import UndoHandler
+from Widgets.Table import *
+
+from Views.EditorFrameView import *
+
+import subprocess
FONT_SIZES = [7, 8, 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288]
-#builds the application's UI
def build_ui(editor):
- print("Building UI...")
+ print("Building UI")
+ editor.setWindowTitle("OpenNote")
+ editor.setWindowIcon(QIcon('./Assets/OpenNoteLogo.png'))
+ editor.resize(800, 450)
+ editor.setAcceptDrops(True)
- #editor.statusBar = editor.statusBar()
- build_window(editor)
- build_menubar(editor)
- #build_toolbar(editor)
- # Application's main layout (grid)
- gridLayout = QGridLayout()
- gridContainerWidget = QWidget()
- editor.setCentralWidget(gridContainerWidget)
- gridContainerWidget.setLayout(gridLayout)
+ # Checks wether mac is in dark mode, then sets proper window css
+ if check_appearance() == False:
+ with open('./Styles/styles.qss',"r") as fh:
+ editor.setStyleSheet(fh.read())
+ else:
+ with open('./Styles/stylesDark.qss',"r") as fh:
+ editor.setStyleSheet(fh.read())
- gridLayout.setSpacing(3)
- gridLayout.setContentsMargins(6, 6, 0, 0)
+ build_tabbar(editor)
+ # build_menubar(editor)
+ # build_toolbar(editor)
- gridLayout.setColumnStretch(0, 1) # The left side (index 0) will take up 1/7? of the space of the right
+ # Main layout of the app
+ gridLayout = QGridLayout()
+ gridLayout.setSpacing(0)
+ gridLayout.setContentsMargins(0, 0, 0, 0)
gridLayout.setColumnStretch(1, 7)
- # Left side of the app's layout
+ centralWidget = QWidget()
+ centralWidget.setLayout(gridLayout)
+
+ # Sets up layout of each bar
+ topSideLayout = QVBoxLayout()
+ topSideContainerWidget = QWidget()
+ topSideContainerWidget.setLayout(topSideLayout)
+ topSideLayout.setContentsMargins(0, 0, 0, 0)
+ topSideLayout.setSpacing(0)
+
+ barsLayout = QVBoxLayout()
+ barsLayoutContainerWidget = QWidget()
+ barsLayoutContainerWidget.setLayout(barsLayout)
+ barsLayout.setContentsMargins(7, 0, 7, 0)
+ barsLayout.setSpacing(0)
+
+ # Add the bars to the layout with individual margins
+ barsLayout.addWidget(editor.tabbar)
+ # barsLayout.addWidget(editor.menubar)
+ # barsLayout.addWidget(editor.homeToolbar)
+ # barsLayout.addWidget(editor.insertToolbar)
+ # barsLayout.addWidget(editor.drawToolbar)
+
+ topSideLayout.addWidget(editor.titlebar, 0)
+ topSideLayout.addWidget(barsLayoutContainerWidget, 1)
+ # topSideLayout.addWidget(editor.homeToolbar, 2)
+ # topSideLayout.addWidget(editor.insertToolbar, 2)
+ # topSideLayout.addWidget(editor.drawToolbar, 2)
+
+ # Sets up left side notebook view
leftSideLayout = QVBoxLayout()
leftSideContainerWidget = QWidget()
leftSideContainerWidget.setLayout(leftSideLayout)
leftSideLayout.setContentsMargins(0, 0, 0, 0)
leftSideLayout.setSpacing(0)
- # Right side of the app's layout
+ leftSideLayout.addWidget(editor.notebookTitleView, 0)
+ leftSideLayout.addWidget(editor.pageView, 1)
+
+ # Sets up right side section view
rightSideLayout = QVBoxLayout()
rightSideContainerWidget = QWidget()
rightSideContainerWidget.setLayout(rightSideLayout)
rightSideLayout.setContentsMargins(0, 0, 0, 0)
rightSideLayout.setSpacing(0)
- rightSideLayout.setStretch(0, 0)
- rightSideLayout.setStretch(1, 1)
- # Add appropriate widgets (ideally just view controllers) to their layouts
- leftSideLayout.addWidget(editor.notebookTitleView, 0)
- leftSideLayout.addWidget(editor.pageView, 1) # Page view has max stretch factor
rightSideLayout.addWidget(editor.sectionView, 0)
- rightSideLayout.addWidget(editor.frameView, 1) # Frame view has max stretch factor
+ rightSideLayout.addWidget(editor.frameView, 1)
- # Add L+R container's widgets to the main grid
- gridLayout.addWidget(leftSideContainerWidget, 0, 0)
- gridLayout.addWidget(rightSideContainerWidget, 0, 1)
+ gridLayout.addWidget(topSideContainerWidget, 0, 0, 1, 2, alignment = Qt.AlignmentFlag.AlignTop)
+ gridLayout.addWidget(leftSideContainerWidget, 1, 0, 1, 1)
+ gridLayout.addWidget(rightSideContainerWidget, 1, 1, 1, 2)
+
+ editor.setCentralWidget(centralWidget)
+
+ #Saves window size
+ #editor.restoreGeometry(editor.settings.value("geometry", editor.saveGeometry()))
+ #editor.restoreState(editor.settings.value("windowState", editor.saveState()))
+
+# Constructs the tab bar with different tabs like File, Home, Insert, Draw, and Plugins
+# It adds toolbars to these tabs and connects them to their functions
+def build_tabbar(editor):
+ editor.tabbar = QTabWidget()
+ editor.tabbar.setTabsClosable(False)
+
+ editor.fileToolbar = QToolBar()
+ editor.homeToolbar = QToolBar()
+ editor.insertToolbar = QToolBar()
+ editor.drawToolbar = QToolBar()
+ editor.pluginToolbar = QToolBar()
-def build_window(editor):
- editor.setWindowTitle("OpenNote")
- editor.setWindowIcon(QIcon('./Assets/OpenNoteLogo.png'))
- editor.setAcceptDrops(True)
- with open('./Styles/styles.qss',"r") as fh:
- editor.setStyleSheet(fh.read())
-def build_menubar(editor):
- file = editor.menuBar().addMenu('&File')
- plugins = editor.menuBar().addMenu('&Plugins')
+ # Toolbars
+ # Constructs toolbars for different functionalities like home, insert, and draw
+ # It adds actions and buttons to these toolbars and connects them to their functions
+ # ---------------------------------------------------------------------------------
- new_file = build_action(editor, 'assets/icons/svg_file_open', 'New Notebook...', 'New Notebook', False)
+ def handleCheck(action):
+ action.setChecked(True)
+ #editorSignalsInstance.checkMade.connect(editor.checkMade)
+
+ # For adding space to the left the first button added to a toolbar
+ spacer1 = QWidget()
+ spacer1.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ spacer1.setFixedWidth(3)
+
+ spacer2 = QWidget()
+ spacer2.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ spacer2.setFixedWidth(7)
+
+ spacer3 = QWidget()
+ spacer3.setSizePolicy(QSizePolicy.Expanding, QSizePolicy.Preferred)
+ spacer3.setFixedWidth(3)
+
+ # ---------------------------------------------------------------------------------
+ # fileToolbar code
+
+ editor.fileToolbar.setObjectName('fileToolbar')
+ editor.fileToolbar.setIconSize(QSize(18,18))
+ editor.fileToolbar.setMovable(False)
+ editor.fileToolbar.setVisible(False)
+ editor.fileToolbar.setFixedHeight(40)
+ editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, editor.fileToolbar)
+
+ new_file = build_button(editor.fileToolbar, './Assets/icons/svg_file_open', 'New Notebook', 'New Notebook', False)
new_file.setShortcut(QKeySequence.StandardKey.New)
- new_file.triggered.connect(lambda: new(editor))
+ new_file.clicked.connect(lambda: new(editor))
- open_file = build_action(editor, 'assets/icons/svg_file_open', 'Open Notebook...', 'Open Notebook', False)
+ open_file = build_button(editor.fileToolbar, './Assets/icons/svg_file_open', 'Open Notebook', 'Open Notebook', False)
open_file.setShortcut(QKeySequence.StandardKey.Open)
- open_file.triggered.connect(lambda: load(editor))
+ open_file.clicked.connect(lambda: load(editor))
- save_file = build_action(editor, 'assets/icons/svg_file_save', 'Save Notebook', 'Save Notebook', False)
+ save_file = build_button(editor.fileToolbar, './Assets/icons/svg_file_save', 'Save Notebook', 'Save Notebook', False)
save_file.setShortcut(QKeySequence.StandardKey.Save)
- save_file.triggered.connect(lambda: save(editor))
+ save_file.clicked.connect(lambda: save(editor))
- save_fileAs = build_action(editor, 'assets/icons/svg_file_save', 'Save Notebook As...', 'Save Notebook As', False)
+ save_fileAs = build_button(editor.fileToolbar, './Assets/icons/svg_file_save', 'Save Notebook As...', 'Save Notebook As', False)
save_fileAs.setShortcut(QKeySequence.fromString('Ctrl+Shift+S'))
- save_fileAs.triggered.connect(lambda: saveAs(editor))
+ save_fileAs.clicked.connect(lambda: saveAs(editor))
+
+ editor.fileToolbar.addWidget(new_file)
+ editor.fileToolbar.addWidget(open_file)
+ editor.fileToolbar.addWidget(save_file)
+ editor.fileToolbar.addWidget(save_fileAs)
+
+ # ---------------------------------------------------------------------------------
+ # homeToolbar code
+
+ editor.homeToolbar.setObjectName('homeToolbar')
+ editor.homeToolbar.setIconSize(QSize(18, 18))
+ editor.homeToolbar.setMovable(False)
+ editor.homeToolbar.setFixedHeight(40)
+ editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, editor.homeToolbar)
- file.addActions([new_file, open_file, save_file, save_fileAs])
+ paste = build_action(editor.homeToolbar, './Assets/icons/svg_paste', "Paste", "Paste", False)
+ paste.triggered.connect(editor.frameView.toolbar_paste)
-def build_toolbar(editor):
- toolbar = QToolBar()
- toolbar.setIconSize(QSize(15, 15))
- toolbar.setMovable(False)
- editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, toolbar)
+ cut = build_action(editor.homeToolbar, './Assets/icons/svg_cut', "Cut", "Cut", False)
+ cut.triggered.connect(lambda: editorSignalsInstance.widgetCut.emit(DraggableContainer))
+
+ copy = build_action(editor.homeToolbar, './Assets/icons/svg_copy', "Copy", "Copy", False)
+ copy.triggered.connect(lambda: editorSignalsInstance.widgetCopied.emit())
- font = QFontComboBox()
- font.currentFontChanged.connect(lambda x: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Font, font.currentFont()))
+ font_family = QFontComboBox()
+ font_family.setObjectName("Font")
+ font_family.setFixedWidth(150)
+ default_font = font_family.currentFont().family()
+ print(f"default font is {default_font}")
+ font_family.currentFontChanged.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Font, font_family.currentFont().family()))
- size = QComboBox()
- size.addItems([str(fs) for fs in FONT_SIZES])
- size.currentIndexChanged.connect(lambda x: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontSize, int(size.currentText())))
+ font_size = QComboBox()
+ font_size.setObjectName("Font Size")
+ font_size.setFixedWidth(50)
+ font_size.addItems([str(fs) for fs in FONT_SIZES])
+ # default text size is 11
+ default_font_size_index = 4
+ font_size.setCurrentIndex(default_font_size_index)
+ font_size.currentIndexChanged.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontSize, int(font_size.currentText())))
- fontColor = build_action(toolbar, 'assets/icons/svg_font_color', "Font Color", "Font Color", False)
- fontColor.triggered.connect(lambda x: openGetColorDialog(purpose = "font"))
+ #current issues:
+ # - Alternates between working and not working
+ # - Textboxes do not remember settings like if font is toggled or current font size
- bgColor = build_action(toolbar, 'assets/icons/svg_font_bucket', "Text Box Color", "Text Box Color", False)
- bgColor.triggered.connect(lambda x: openGetColorDialog(purpose = "background"))
+ bgColor = build_action(editor.homeToolbar, './Assets/icons/svg_font_bucket', "Background Color", "Background Color", False)
+ #bgColor.triggered.connect(lambda: openGetColorDialog(purpose = "background"))
+ #current bug, alternates between activating and not working when using
+ bgColor.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.BackgroundColor, QColorDialog.getColor()))
- bold = build_action(toolbar, 'assets/icons/bold', "Bold", "Bold", True)
- bold.triggered.connect(lambda x: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontBold, None))
+ textHighlightColor = build_action(editor.homeToolbar, './Assets/icons/svg_textHighlightColor', "Text Highlight Color", "Text Highlight Color", True)
- italic = build_action(toolbar, 'assets/icons/italic.svg', "Italic", "Italic", True)
- italic.triggered.connect(lambda x: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontItalic, None))
+ textHighlightColor.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.TextHighlightColor, QColorDialog.getColor()))
- underline = build_action(toolbar, 'assets/icons/underline.svg', "Underline", "Underline", True)
- underline.triggered.connect(lambda x: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontUnderline, None))
+ #defines font color icon appearance and settings
+ fontColor = build_action(editor.homeToolbar, './Assets/icons/svg_font_color', "Font Color", "Font Color", False)
+ fontColor.triggered.connect(lambda: openGetColorDialog(purpose = "font"))
- toolbar.addWidget(font)
- toolbar.addWidget(size)
- toolbar.addActions([bgColor, fontColor, bold, italic, underline])
+ bold = build_action(editor.homeToolbar, './Assets/icons/bold', "Bold", "Bold", True)
+ bold.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontBold, None))
+ italic = build_action(editor.homeToolbar, './Assets/icons/italic.svg', "Italic", "Italic", True)
+ italic.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontItalic, None))
+
+ underline = build_action(editor.homeToolbar, './Assets/icons/underline.svg', "Underline", "Underline", True)
+ underline.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontUnderline, None))
+
+ strikethrough = build_action(editor.homeToolbar, './Assets/icons/svg_strikethrough.svg', "Strikethrough", "Strikethrough", True)
+ strikethrough.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Strikethrough, None))
+
+ refactor = build_action(editor.homeToolbar, './Assets/icons/bold', "Bold", "Bold", True)
+ refactor.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Refactor, None))
+
+ delete = build_action(editor.homeToolbar, './Assets/icons/svg_delete', "Delete", "Delete", False)
+ delete.triggered.connect(lambda: editorSignalsInstance.widgetRemoved.emit(DraggableContainer))
+
+ # Bullets with placeholder for more bullet options
+ bullets = build_action(editor.homeToolbar, './Assets/icons/svg_bullets', "Bullets", "Bullets", False)
+ bullets.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Bullet, None))
+
+
+ # Adds the undo and redo buttons to the toolbar
+ undo = build_action(editor.homeToolbar, './Assets/icons/svg_undo', "Undo", "Undo", False)
+ redo = build_action(editor.homeToolbar, './Assets/icons/svg_redo', "Redo", "Redo", False)
+
+ editor.homeToolbar.addWidget(spacer1)
+ editor.homeToolbar.addActions([paste, cut, copy])
+
+ editor.homeToolbar.addSeparator()
+
+ editor.homeToolbar.addWidget(font_family)
+ editor.homeToolbar.addWidget(font_size)
+
+ editor.homeToolbar.addSeparator()
+
+ editor.homeToolbar.addActions([undo, redo, bold, italic, underline, strikethrough, fontColor, textHighlightColor, bgColor, delete, bullets])
+
+ # numbering menu start
+ numbering_menu = QMenu(editor)
+
+ bullet_num = build_action(numbering_menu, './Assets/icons/svg_bullet_number', "", "", False)
+ bullet_num.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Bullet_Num, None))
+
+ bullet_num = build_action(numbering_menu, './Assets/icons/svg_bullet_number', "", "", False)
+ bullet_num.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Bullet_Num, None))
+ bulletUpperA = build_action(numbering_menu, './Assets/icons/svg_bulletUA', "", "", False)
+ bulletUpperA.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.BulletUA, None))
+ bulletUpperR = build_action(numbering_menu, './Assets/icons/svg_bulletUR', "", "", False)
+ bulletUpperR.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.BulletUR, None))
+
+ numbering_menu.addActions([bullet_num, bulletUpperA, bulletUpperR])
+
+ numbering = build_menubutton(editor, './Assets/icons/svg_bullet_number', "", "Numbering", "width:35px;", numbering_menu)
+
+ editor.homeToolbar.addWidget(numbering)
+
+ # QActionGroup used to display that only one can be toggled at a time
+ align_group = QActionGroup(editor.homeToolbar)
+
+ align_left = build_action(align_group,"./Assets/icons/svg_align_left","Align Left","Align Left", True)
+ align_left.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignLeft, None))
+ align_center = build_action(align_group,"./Assets/icons/svg_align_center","Align Center","Align Center", True)
+ align_center.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignCenter, None))
+ align_right = build_action(align_group, "./Assets/icons/svg_align_right", "Align Right", "Align Right", True)
+ align_right.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignRight, None))
+
+ align_group.addAction(align_left)
+ align_group.addAction(align_center)
+ align_group.addAction(align_right)
+ editor.homeToolbar.addActions(align_group.actions())
+
+ editor.homeToolbar.addSeparator()
+
+
+ # ---------------------------------------------------------------------------------
+ # Classic Home Toolbar
+
+ editor.classicToolbar = QToolBar()
+ editor.classicToolbar.setObjectName('classicHomeToolbar')
+ editor.classicToolbar.setIconSize(QSize(18,18))
+ editor.classicToolbar.setFixedHeight(40)
+ editor.classicToolbar.setMovable(False)
+ editor.classicToolbar.setVisible(False)
+ editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, editor.classicToolbar)
+
+
+
+ # ---------------------------------------------------------------------------------
+ # insertToolbar code
+
+ editor.insertToolbar.setObjectName('insertToolbar')
+ editor.insertToolbar.setIconSize(QSize(18,18))
+ editor.insertToolbar.setFixedHeight(40)
+ editor.insertToolbar.setMovable(False)
+ editor.insertToolbar.setVisible(False)
+ editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, editor.insertToolbar)
+
+ table = build_button(editor.insertToolbar, './Assets/icons/svg_table', "Table", "Add a Table", False)
+ table.clicked.connect(editor.frameView.toolbar_table)
+
+ insertSpace = build_button(editor.insertToolbar, './Assets/icons/svg_insert_space', "Insert Space", "Insert Space", False)
+
+ screensnip = build_button(editor.insertToolbar, './Assets/icons/svg_screensnip', "Screensnip", "Screensnip", False)
+ screensnip.clicked.connect(editor.frameView.toolbar_snipScreen)
+
+ pictures = build_button(editor.insertToolbar, './Assets/icons/svg_pictures', "Pictures" , "Pictures", False)
+ pictures.clicked.connect(editor.frameView.toolbar_pictures)
+
+ hyperlink = build_button(editor.insertToolbar, './Assets/icons/svg_hyperlink', "Hyperlink", "Hyperlink", False)
+ hyperlink.clicked.connect(editor.frameView.toolbar_hyperlink)
+
+ # date and time menu start
+ dateTime_menu = QMenu(editor)
+
+ date = build_action(dateTime_menu, './Assets/icons/svg_date', "Date", "Date", False)
+ date.triggered.connect(editor.frameView.toolbar_date)
+
+ time = build_action(dateTime_menu, './Assets/icons/svg_time', "Time", "Time", False)
+ time.triggered.connect(editor.frameView.toolbar_time)
+
+ dateTime_menu.addActions([date, time])
+
+ dateTime = build_menubutton(editor, './Assets/icons/svg_dateTime', "Date && Time", "Date & Time", "width:120px;", dateTime_menu)
+
+ editor.insertToolbar.addWidget(spacer2)
+
+ editor.insertToolbar.addWidget(table)
+
+ editor.insertToolbar.addSeparator()
+
+ editor.insertToolbar.addWidget(insertSpace)
+
+ editor.insertToolbar.addSeparator()
+
+ editor.insertToolbar.addWidget(screensnip)
+ editor.insertToolbar.addWidget(pictures)
+
+ editor.insertToolbar.addSeparator()
+
+ editor.insertToolbar.addWidget(hyperlink)
+
+ editor.insertToolbar.addSeparator()
+
+ editor.insertToolbar.addWidget(dateTime)
+
+ # ---------------------------------------------------------------------------------
+ # drawToolbar code
+
+ editor.drawToolbar.setObjectName('drawToolbar')
+ editor.drawToolbar.setIconSize(QSize(18, 18))
+ editor.drawToolbar.setFixedHeight(40)
+ editor.drawToolbar.setMovable(False)
+ editor.drawToolbar.setVisible(False)
+ editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, editor.drawToolbar)
+
+ undo_action = QAction(QIcon('./Assets/icons/undo.png'), '&Undo', editor)
+ undo.triggered.connect(editor.triggerUndo)
+
+ redo_action = QAction(QIcon('./Assets/icons/redo.png'), '&Redo', editor)
+ redo.triggered.connect(editor.triggerRedo)
+ # redo.triggered.connect(editor.frameView.triggerRedo)
+
+ paperColor= build_action(editor.drawToolbar, './Assets/icons/svg_paper', "Paper Color", "Paper Color", False)
+ paperColor.triggered.connect(lambda: editor.frameView.pageColor(QColorDialog.getColor()))
+
+ editor.drawToolbar.addWidget(spacer3)
+ editor.drawToolbar.addActions([undo, redo])
+
+ editor.drawToolbar.addSeparator()
+
+ editor.drawToolbar.addAction(paperColor)
+
+ # ---------------------------------------------------------------------------------
+ # pluginToolbar code
+
+ editor.pluginToolbar.setObjectName('pluginToolbar')
+ editor.pluginToolbar.setIconSize(QSize(18,18))
+ editor.pluginToolbar.setFixedHeight(40)
+ editor.pluginToolbar.setMovable(False)
+ editor.pluginToolbar.setVisible(False)
+ editor.addToolBar(Qt.ToolBarArea.TopToolBarArea, editor.pluginToolbar)
+
+ add_widget = build_button(editor, './Assets/icons/svg_question', 'Add Custom Widget', 'Add Custom Widget', False)
+
+ editor.pluginToolbar.addWidget(add_widget)
+ # ---------------------------------------------------------------------------------
+
+ editor.tabbar.addTab(editor.fileToolbar, '&File')
+ editor.tabbar.addTab(editor.homeToolbar, '&Home')
+ editor.tabbar.addTab(editor.insertToolbar, '&Insert')
+ editor.tabbar.addTab(editor.drawToolbar, '&Draw')
+ editor.tabbar.addTab(editor.pluginToolbar, '&Plugins')
+
+ # Sets the first shown tab as the home tab
+ editor.tabbar.setCurrentIndex(1)
+
+def check_appearance():
+ """Checks DARK/LIGHT mode of macos."""
+ cmd = 'defaults read -g AppleInterfaceStyle'
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True)
+ return bool(p.communicate()[0])
+
+# toggles the visibility of the specified toolbar based on user interaction
+def set_toolbar_visibility(editor, triggered_toolbar):
+ # Find all toolbars in the editor
+ toolbars = editor.findChildren(QToolBar)
+
+ # Iterate over each toolbar
+ for toolbar in toolbars:
+ if toolbar.objectName() == triggered_toolbar:
+ # Toggle the visibility of the triggered toolbar
+ print(toolbar.objectName(),"visibility change")
+ toolbar.setVisible(not toolbar.isVisible())
+ else:
+ # Hide all other toolbars
+ toolbar.setVisible(False)
+
+# opens a color dialog for selecting font color or background color
def openGetColorDialog(purpose):
color = QColorDialog.getColor()
if color.isValid():
@@ -125,7 +450,34 @@ def openGetColorDialog(purpose):
elif purpose == "background":
editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.BackgroundColor, color)
-def build_action(parent, icon_path, action_name, set_status_tip, set_checkable):
+# creates a QAction object with the specified parameters
+def build_action(parent, icon_path, action_name, tooltip, checkable):
action = QAction(QIcon(icon_path), action_name, parent)
- action.setStatusTip(set_status_tip)
+ action.setObjectName(action_name)
+ action.setStatusTip(tooltip)
+ action.setCheckable(checkable)
return action
+
+# creates a QPushButton widget with the specified parameters
+def build_button(parent, icon_path, text, tooltip, checkable):
+ button = QPushButton(parent)
+ button.setIcon(QIcon(icon_path))
+ button.setObjectName(text)
+ button.setText(text)
+ button.setToolTip(tooltip)
+ button.setCheckable(checkable)
+ return button
+
+# creates a QToolButton widget with an associated menu
+def build_menubutton(parent, icon_path, text, tooltip, style, menu):
+ button = QToolButton(parent)
+ button.setIconSize(QSize(18,18))
+ button.setToolButtonStyle(Qt.ToolButtonTextBesideIcon)
+ button.setPopupMode(QToolButton.MenuButtonPopup)
+ button.setIcon(QIcon(icon_path))
+ button.setText(text)
+ button.setToolTip(tooltip)
+ button.setObjectName(tooltip)
+ button.setStyleSheet(style)
+ button.setMenu(menu)
+ return button
diff --git a/Modules/Clipboard.py b/Modules/Clipboard.py
index e67c7d9..aad99b6 100644
--- a/Modules/Clipboard.py
+++ b/Modules/Clipboard.py
@@ -2,17 +2,21 @@
class Clipboard:
def __init__(self):
+
+ # Initialize variables to store copied widget state and class
self.copiedWidgetState = None
self.copiedWidgetClass = None
editorSignalsInstance.widgetCopied.connect(self.copyWidgetEvent)
+ # triggered when a widget is copied
def copyWidgetEvent(self, draggableContainer):
- widget = draggableContainer.childWidget
-
+ widget = draggableContainer.childWidget # Get the child widget from the draggable container
+ print("copy widget")
self.copiedWidgetClass = type(widget)
self.copiedWidgetState = widget.__getstate__()
+ #
def getWidgetToPaste(self):
widgetState = self.copiedWidgetState
widgetClass = self.copiedWidgetClass
@@ -20,4 +24,4 @@ def getWidgetToPaste(self):
newWidget = widgetClass.__new__(widgetClass) # Get uninitialized instance of widget class
newWidget.__setstate__(widgetState) # Initialize the widget instance with its setstate method
- return newWidget
+ return newWidget # Return the initialized widget instance
diff --git a/Modules/EditorSignals.py b/Modules/EditorSignals.py
index 97d3873..65dd164 100644
--- a/Modules/EditorSignals.py
+++ b/Modules/EditorSignals.py
@@ -3,6 +3,8 @@
from enum import Enum
class ChangedWidgetAttribute(Enum):
+ # Used for unique signals
+ # Add variable to create a unique signal
BackgroundColor = 0
FontColor = 1
Font = 2
@@ -10,7 +12,23 @@ class ChangedWidgetAttribute(Enum):
FontBold = 4
FontItalic = 5
FontUnderline = 6
-
+ TextHighlightColor = 7
+ Bullet = 8
+ Bullet_Num = 9
+ LoseFocus = 10
+
+ BulletUR = 11
+ BulletUA = 12
+ AlignLeft = 13
+ AlignCenter = 14
+ AlignRight = 15
+ PaperColor = 16
+
+ Strikethrough = 17
+ Refactor = 18
+
+class CheckSignal(Enum):
+ BoldCheck = 0
# Cant be statically typed because importing the classes causes circular imports
class EditorSignals(QObject):
@@ -23,7 +41,7 @@ class EditorSignals(QObject):
widgetRemoved = Signal(object)
widgetCopied = Signal(object)
widgetCut = Signal(object)
-
+
# Recieves any widget model, and the section model to add the instance of DraggableContainer to
widgetShouldLoad = Signal(object, object)
@@ -33,4 +51,10 @@ class EditorSignals(QObject):
# Recieves nothing, used by autosaver
changeMade = Signal()
+ # Clear Selection
+ loseFocus = Signal()
+
+ # used for checking if a toggle should be toggled off
+ checkMade = Signal()
+
editorSignalsInstance = EditorSignals()
diff --git a/Modules/Load.py b/Modules/Load.py
index b618271..5add389 100644
--- a/Modules/Load.py
+++ b/Modules/Load.py
@@ -2,7 +2,7 @@
import os
from Modules.Save import Autosaver
-
+import pyautogui
from PySide6.QtWidgets import *
from PySide6.QtCore import *
from PySide6.QtGui import *
@@ -13,8 +13,8 @@
def new(editor):
print("RAN NEW")
destroy(editor)
-
- editor.notebook = NotebookModel('Untitled')
+ p_name = pyautogui.prompt("Enter Notebook Name")
+ editor.notebook = NotebookModel(p_name)
editor.notebookTitleView.setText(editor.notebook.title)
editor.selected = None
editor.autosaver = Autosaver(editor)
@@ -23,6 +23,8 @@ def new(editor):
# Loads models.notebook.Notebook class from file
def load(editor):
print("LOADING")
+
+ # Open file dialog for selecting notebook file
path, accept = QFileDialog.getOpenFileName(
editor,
'Open Notebook',
@@ -47,6 +49,7 @@ def load(editor):
def load_most_recent_notebook(editor):
print("LOAD RECENT RAN")
+ # Search for most recent notebook file
files = []
saves_directory = os.path.join(os.getcwd(), 'Saves')
for file in os.listdir(saves_directory):
@@ -60,6 +63,7 @@ def load_most_recent_notebook(editor):
if (f.endswith(".on") or f.endswith(".ontemp")):
print("FOUND: " + str(f))
try:
+ # Open and load the notebook
# prob need load from file function, dup functionality
file = open(os.path.join(os.getcwd() + "\\Saves", f), 'rb')
destroy(editor)
diff --git a/Modules/Multiselect.py b/Modules/Multiselect.py
index 3d656da..d07b7c5 100644
--- a/Modules/Multiselect.py
+++ b/Modules/Multiselect.py
@@ -5,6 +5,7 @@
from PySide6.QtGui import *
from PySide6.QtWidgets import *
from Models.DraggableContainer import DraggableContainer
+from Modules.EditorSignals import editorSignalsInstance,ChangedWidgetAttribute
class MultiselectMode(Enum):
NONE = 0,
@@ -26,6 +27,24 @@ def __init__(self, editorFrame):
self.dragInitEventPos = None # Position of the event that started the dragging
self.dragOffset = None # Offset of object that is used to drag the others from the first object in the editors list
+
+ # for handling deselect
+ self.installEventFilter()
+ # Connect signal for widget removal
+ editorSignalsInstance.widgetRemoved.connect(self.removeWidgetFromList)
+
+ editorSignalsInstance.widgetCut.connect(self.cutWidgetEvent)
+
+ # Slot to remove widget from object list
+ @Slot(QWidget)
+ def removeWidgetFromList(self, widget):
+ if widget in self.selectedObjects:
+ self.selectedObjects.remove(widget)
+
+ # install event filter to editorframe
+ def installEventFilter(self):
+ self.editorFrame.installEventFilter(self)
+
def eventFilter(self, obj, event):
multiselector = self
@@ -55,8 +74,31 @@ def eventFilter(self, obj, event):
multiselector.focusObjectIfInMultiselect()
return False
+
+ # Handle click outside the selected objects to deselect
+ if event.type() == QEvent.MouseButtonPress and event.button() == Qt.LeftButton:
+ if multiselector.mode == MultiselectMode.HAS_SELECTED_OBJECTS:
+ multiselector.deselectIfClickOutsideObjects(event)
+ print("DESELECT from multiselect")
+
return False
+ # check if clicked outside
+ def deselectIfClickOutsideObjects(self, event):
+ # Check if the click is outside any selected objects
+ clickPos = event.globalPos()
+ for o in self.selectedObjects:
+ o_tl_pos = o.mapToGlobal(QPoint(0, 0))
+ o_br_pos = o_tl_pos + QPoint(o.width(), o.height())
+
+ if o_tl_pos.x() <= clickPos.x() <= o_br_pos.x() and o_tl_pos.y() <= clickPos.y() <= o_br_pos.y():
+ # Click is inside a selected object, do not deselect'
+ print("Click is inside a selected object, do not deselect")
+ return
+
+ # Click is outside all selected objects, deselect
+ self.finishDraggingObjects()
+
def beginDrawingArea(self, event):
self.mode = MultiselectMode.IS_DRAWING_AREA
self.drawingWidget.setStyleSheet(TextBoxStyles.INFOCUS.value)
@@ -66,38 +108,43 @@ def beginDrawingArea(self, event):
self.drawAreaStartGlobalPos = event.globalPos()
def continueDrawingArea(self, event):
- width = event.pos().x() - self.drawAreaStartLocalPos.x()
- height = event.pos().y() - self.drawAreaStartLocalPos.y()
+ start_x = self.drawAreaStartLocalPos.x()
+ start_y = self.drawAreaStartLocalPos.y()
- self.drawingWidget.resize(width, height)
+ end_x = event.pos().x()
+ end_y = event.pos().y()
+ width = abs(end_x - start_x)
+ height = abs(end_y - start_y)
+
+ # Calculate the x and y for setting the geometry
+ x = min(start_x, end_x)
+ y = min(start_y, end_y)
+
+ # Adjust the geometry of the drawingWidget
+ self.drawingWidget.setGeometry(x, y, width, height)
def finishDrawingArea(self, event):
editor = self.editorFrame.editor
- # Throw away if the area of the selection is negative (user dragged from bottom right to top left)
- if self.drawAreaStartGlobalPos.x() > event.globalPos().x() or self.drawAreaStartGlobalPos.y() > event.globalPos().y():
- self.drawingWidget.hide()
- self.finishDraggingObjects()
- # You could probably switch around the coordinates in here and modify drawing logic to support other drag directions
- return
+ # Get the coordinates of the top-left corner of the selection area
+ start_x = min(self.drawAreaStartGlobalPos.x(), event.globalPos().x())
+ start_y = min(self.drawAreaStartGlobalPos.y(), event.globalPos().y())
- # Get all DraggableContainers in the selection
+ # Get the coordinates of the bottom-right corner of the selection area
+ end_x = max(self.drawAreaStartGlobalPos.x(), event.globalPos().x())
+ end_y = max(self.drawAreaStartGlobalPos.y(), event.globalPos().y())
+
+ # Iterate through objects and check if they are inside the selection area
sectionView = self.editorFrame.editor.sectionView
currentSectionIndex = sectionView.tabs.currentIndex()
currentSectionModel = sectionView.sectionModels[currentSectionIndex]
currentSectionModelWidgets = currentSectionModel.widgets
- for o in currentSectionModelWidgets:
- print(o)
- # Map all positions to global for correct coord checks
- ob_tl_pos = o.mapToGlobal(QPoint(0, 0)) # Object top left corner
- start_pos = self.drawAreaStartGlobalPos
- end_pos = event.globalPos()
+ for o in currentSectionModelWidgets:
+ ob_tl_pos = o.mapToGlobal(QPoint(0, 0))
- # If object x + width is between start and end x, and object y + height is between start and end y
- if ob_tl_pos.x() > start_pos.x() and ob_tl_pos.x() + o.width() < end_pos.x():
- if ob_tl_pos.y() > start_pos.y() and ob_tl_pos.y() + o.height() < end_pos.y():
- self.selectedObjects.append(o)
+ if start_x <= ob_tl_pos.x() <= end_x and start_y <= ob_tl_pos.y() <= end_y:
+ self.selectedObjects.append(o)
if len(self.selectedObjects) > 0:
self.mode = MultiselectMode.HAS_SELECTED_OBJECTS
@@ -106,6 +153,13 @@ def finishDrawingArea(self, event):
print("SELECTION COUNT: ", len(self.selectedObjects))
+ # highlight all text for textwidgets
+ for o in self.selectedObjects:
+ # Checks if child is textwidget
+ if (isinstance(o.childWidget, QTextEdit)):
+ print("TEXT WIDGET FOUND. HIGHLIGHTING")
+ # Call function in textwidget to highlight all text in their textbox
+ o.childWidget.selectAllText()
# Hide selection area
self.drawingWidget.hide()
@@ -120,7 +174,7 @@ def dragObjects(self, event):
# Get the position that each object would move to
objectPositions = []
- for o in reversed(self.selectedObjects): # fuck it, reversed
+ for o in reversed(self.selectedObjects): # fuck it, reversed
# You have to know this, focus
# Position of the first (in the reversed array) object's top left corner + the offset of the click inside the selected object - the position of the object to move
@@ -179,3 +233,15 @@ def focusObjectIfInMultiselect(self):
o.setStyleSheet(TextBoxStyles.INFOCUS.value)
except:
self.finishDraggingObjects()
+ # Allow removing all selected objects
+ def removeWidgetEvent(self):
+ for obj in self.selectedObjects:
+ editorSignalsInstance.changeMade.emit(obj)
+
+ # Allow cutting all selected objects
+ def cutWidgetEvent(self):
+ for obj in self.selectedObjects:
+ editorSignalsInstance.widgetCopied.emit(obj)
+ editorSignalsInstance.widgetRemoved.emit(obj)
+
+ # Paste works from clipboard, meaning have to store all objects to clipboard
\ No newline at end of file
diff --git a/Modules/Screensnip.py b/Modules/Screensnip.py
index 86950eb..9dd22f9 100644
--- a/Modules/Screensnip.py
+++ b/Modules/Screensnip.py
@@ -13,43 +13,58 @@ class SnippingWidget(QWidget):
def __init__(self):
super(SnippingWidget, self).__init__()
- self.setWindowFlags(Qt.WindowStaysOnTopHint)
+
+ # Set window attributes based on the platform
+ if platform == "linux":
+ self.setWindowFlags(Qt.FramelessWindowHint)
+ self.setAttribute(Qt.WA_TranslucentBackground)
+
+ self.setWindowFlags(Qt.WindowStaysOnTopHint)
self.parent = None
self.screen = QApplication.instance().primaryScreen()
self.setGeometry(0, 0, self.screen.size().width(), self.screen.size().height())
- self.begin = QPoint()
- self.end = QPoint()
+ self.begin = self.end = QPoint()
self.onSnippingCompleted = None
self.event_pos = None
def start(self, event_pos):
print("SnippingWidget.start")
SnippingWidget.is_snipping = True
- self.setWindowOpacity(0.3)
+
+ # Set window opacity and cursor based on the platform
+ if platform != "linux":
+ self.setWindowOpacity(0.3)
+
QApplication.setOverrideCursor(QCursor(Qt.CrossCursor))
self.event_pos = event_pos
self.show()
print("SnippingWidget.start done")
+ # handle painting the snipping rectangle
def paintEvent(self, event):
if SnippingWidget.is_snipping:
- brush_color = (128, 128, 255, 100)
+ #brush_color = (128, 128, 255, 100)
+ brush_color = (0, 0, 0, 0)
lw = 3
opacity = 0.3
+
+ if platform != "linux":
+ self.setWindowOpacity(opacity)
+
+ qp = QPainter(self)
+ # Set pen and brush for drawing the rectangle
+ qp.setPen(QPen(QColor('black'), lw))
+ qp.setBrush(QColor(*brush_color))
+ rect = QRectF(self.begin, self.end)
+ qp.drawRect(rect)
else:
+ # Reset coordinates and brush color when snipping is not in progress
self.begin = QPoint()
self.end = QPoint()
brush_color = (0, 0, 0, 0)
lw = 0
opacity = 0
- self.setWindowOpacity(opacity)
- qp = QPainter(self)
- qp.setPen(QPen(QColor('black'), lw))
- qp.setBrush(QColor(*brush_color))
- rect = QRectF(self.begin, self.end)
- qp.drawRect(rect)
-
def mousePressEvent(self, event):
self.begin = event.pos()
self.end = self.begin
@@ -60,28 +75,42 @@ def mouseMoveEvent(self, event):
self.update()
def mouseReleaseEvent(self, event):
+
+ # Set the flag to indicate that snipping is complete
SnippingWidget.is_snipping = False
QApplication.restoreOverrideCursor()
- x1 = min(self.begin.x(), self.end.x())
- y1 = min(self.begin.y(), self.end.y())
- x2 = max(self.begin.x(), self.end.x())
- y2 = max(self.begin.y(), self.end.y())
+ # Calculate the coordinates of the snipped area
+ rect = self.geometry()
+ x1 = min(self.begin.x(), self.end.x()) + rect.left()
+ y1 = min(self.begin.y(), self.end.y()) + rect.top()
+ x2 = max(self.begin.x(), self.end.x()) + rect.left()
+ y2 = max(self.begin.y(), self.end.y()) + rect.top()
+
+ # Repaint the widget and process any pending events
self.repaint()
QApplication.processEvents()
- if platform == "darwin":
- img = ImageGrab.grab(bbox=( (x1 ) * 2, (y1 + 55 ) * 2, (x2 ) * 2, (y2 + 55) * 2))
- else:
- img = ImageGrab.grab(bbox=(x1 + 10, y1 + 30, x2 + 10, y2 + 40))
-
+ try:
+ # Capture the screenshot of the snipped area
+ if platform == "darwin":
+ #img = ImageGrab.grab(bbox=( (x1 ) * 2, (y1 + 55 ) * 2, (x2 ) * 2, (y2 + 55) * 2)) [may be needed for different mac version - testing in progress]
+ img = ImageGrab.grab(bbox=(x1, y1 + 55, x2, y2 + 55)) # For mac version 14.1.2
+ else:
+ img = ImageGrab.grab(bbox=(x1 + 2, y1 + 2, x2 - 1, y2 - 1))
+
+ except Exception as e:
+ print(f"Error grabbing screenshot: {e}")
+ img = None
try:
+ # Convert the captured image to OpenCV format for further processing
img = cv2.cvtColor(np.array(img), cv2.COLOR_RGB2BGR)
except:
img = None
-
+
+ # Trigger the snipping completed callback with the captured image
if self.onSnippingCompleted is not None:
self.onSnippingCompleted(img)
diff --git a/Modules/Titlebar.py b/Modules/Titlebar.py
new file mode 100644
index 0000000..dace464
--- /dev/null
+++ b/Modules/Titlebar.py
@@ -0,0 +1,148 @@
+from PySide6.QtCore import *
+from PySide6.QtGui import *
+from PySide6.QtSvg import *
+from PySide6.QtWidgets import *
+
+from Models.DraggableContainer import DraggableContainer
+from Widgets.Textbox import *
+
+from Modules.BuildUI import *
+from Modules.EditorSignals import editorSignalsInstance, ChangedWidgetAttribute
+from Modules.Undo import UndoHandler
+
+from Views.EditorFrameView import *
+
+class Build_titlebar(QWidget):
+ def __init__(self, parent):
+ super().__init__(parent)
+ print("Building Titlebar")
+ self.setAutoFillBackground(True)
+ self.setBackgroundRole(QPalette.ColorRole.Highlight)
+ self.setFixedHeight(49)
+ self.setObjectName("titlebar")
+ # self.setStyleSheet("background-color: rgb(119, 25, 170);")
+ self.initial_pos = None
+
+ titlebarLayout = QHBoxLayout(self)
+ titlebarLayout.setContentsMargins(0, 0, 0, 0)
+ titlebarLayout.setSpacing(0)
+
+ self.logo = build_titlebutton('./Assets/White-OpenNoteLogo', "logo", None)
+ self.logo.setFixedSize(QSize(49, 49))
+
+ self.title = QLabel("OpenNote", self)
+ self.title.setStyleSheet("color: white;")
+ self.title.setAlignment(Qt.AlignmentFlag.AlignCenter)
+ self.title.setMaximumWidth(parent.width() // 5.5)
+ if title := parent.windowTitle():
+ self.title.setText(title)
+
+ self.leftSpacer = QWidget(self)
+ self.leftSpacer.setMinimumWidth(parent.width() // 5.5)
+
+ # Search bar
+ self.searchbarWidget = QWidget(self)
+ self.searchbarLayout = QVBoxLayout()
+ self.searchbarWidget.setLayout(self.searchbarLayout)
+ self.searchbarWidget.setObjectName("searchbarWidget")
+ self.searchbarLayout.setAlignment(Qt.AlignmentFlag.AlignCenter)
+
+ self.searchbar = QLineEdit(self)
+ self.searchbar.setClearButtonEnabled(True)
+ self.searchbar.addAction(QIcon("./Assets/Icons/svg_search"), QLineEdit.LeadingPosition)
+ self.searchbar.setMaximumWidth(parent.width() // 2)
+ self.searchbar.setPlaceholderText("Search")
+ self.searchbar.returnPressed.connect(self.perform_search)
+ self.searchbarLayout.addWidget(self.searchbar)
+
+ self.rightSpacer = QWidget(self)
+ self.rightSpacer.setMinimumWidth(parent.width() // 6)
+
+ # the names for windowed and fullscreen might be swapped, but it works how it should so 👍
+ self.tray = build_titlebutton('./Assets/icons/svg_tray', "tray", self.window().showMinimized)
+ # self.windowed = build_titlebutton('./Assets/icons/svg_windowed', "windowed", self.windowed_window)
+ # self.fullscreen = build_titlebutton('./Assets/icons/svg_fullscreen', "fullscreen", self.fullscreen_window)
+ self.windowed = build_titlebutton('./Assets/icons/svg_windowed', "windowed", self.window().showNormal)
+ self.fullscreen = build_titlebutton('./Assets/icons/svg_fullscreen', "fullscreen", self.window().showMaximized)
+
+ self.close = build_titlebutton('./Assets/icons/svg_close', "close", self.window().close)
+
+ titlebarLayout.addWidget(self.logo)
+
+ titlebarLayout.addWidget(self.title)
+
+ titlebarLayout.addWidget(self.leftSpacer)
+
+ titlebarLayout.addWidget(self.searchbarWidget)
+
+ titlebarLayout.addWidget(self.rightSpacer)
+
+ titlebarLayout.addWidget(self.tray)
+ titlebarLayout.addWidget(self.windowed)
+ titlebarLayout.addWidget(self.fullscreen)
+ titlebarLayout.addWidget(self.close)
+
+ def window_state_changed(self, state):
+ self.windowed.setVisible(state != Qt.WindowState.WindowNoState)
+ self.fullscreen.setVisible(state == Qt.WindowState.WindowNoState)
+
+ def set_action_names(self, action_names):
+ # Populate the completer with search suggestions
+ self.search_suggestions = action_names
+ completer = QCompleter(self.search_suggestions)
+ completer.setCaseSensitivity(Qt.CaseInsensitive)
+ self.searchbar.setCompleter(completer)
+
+ def perform_search(self):
+ search_text = self.searchbar.text().lower()
+
+ # Dictionary mapping search keywords to corresponding actions
+ actions = {
+ "paste": lambda: None, # editor.frameView.toolbar_paste
+ "cut": lambda: None,
+ "copy": lambda: None,
+ "paste": lambda: None,
+ "font": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Font, font_family.currentFont().family()),
+ "font size": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontSize, int(font_size.currentText())),
+ "bold": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontBold, None),
+ "italic": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontItalic, None),
+ "underline": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontUnderline, None),
+ "font color": lambda: openGetColorDialog(purpose = "font"),
+ "text highlight color": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.TextHighlightColor, QColorDialog.getColor()),
+ "strikethrough": lambda: lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Strikethrough, None),
+ "background color": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.BackgroundColor, QColorDialog.getColor()),
+ "delete": lambda: None,
+ "bullets": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Bullet, None),
+ "numbering": None, # QMenu lambda call???
+ "align left": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignLeft, None),
+ "align center": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignCenter, None),
+ "align right": lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignRight, None),
+ "table": None, # editor.frameView.toolbar_table
+ "insert space": None,
+ "screensnip": None, # editor.frameView.toolbar_snipScreen
+ "pictures": None, # editor.frameView.toolbar_pictures
+ "hyperlink": None, # editor.frameView.toolbar_hyperlink
+ "date & time": None, # also qmenu
+ "undo": lambda: None,
+ "redo": lambda: None,
+ "paper color": lambda: None #editor.frameView.pageColor(QColorDialog.getColor()),
+ }
+
+ # Check if the search_text corresponds to a known action, and execute it if found
+ action = actions.get(search_text)
+ if action:
+ action()
+ else:
+ # Handle unknown action
+ pass
+
+def build_titlebutton(icon_path, object_name, on_click):
+ button = QToolButton()
+ button.setIcon(QIcon(icon_path))
+ button.setObjectName(object_name)
+ button.clicked.connect(on_click)
+ button.setFocusPolicy(Qt.FocusPolicy.NoFocus)
+ button.setFixedSize(QSize(49, 49))
+ button.setStyleSheet("QToolButton { border: none; padding: 0px;}")
+ return button
+
\ No newline at end of file
diff --git a/Modules/Undo.py b/Modules/Undo.py
index d44eefc..51b7e55 100644
--- a/Modules/Undo.py
+++ b/Modules/Undo.py
@@ -4,6 +4,8 @@
from Modules.EditorSignals import editorSignalsInstance
import os
+
+#current version of undo does not work. When using ctrl+z in textboxes, it uses the QTextEdit default settings for ctrl+z(undo) and ctrl+y(redo) to make it appear as if undo does work
class UndoActionCreate:
def __init__(self, draggableContainer):
self.draggableContainer = draggableContainer
diff --git a/Styles/styles.qss b/Styles/styles.qss
index 247ef03..cc089fb 100644
--- a/Styles/styles.qss
+++ b/Styles/styles.qss
@@ -2,12 +2,50 @@
border: none;
padding: 0;
margin: 0;
- font-family: "Segoe UI";
- font-size: 16pt;
+ font-family: "calibri", sans-serif;
+ font-size: 12pt;
+}
+
+QMainWindow {
+ background-color: rgb(230, 229, 235);
}
QMenuBar {
- background-color: rgb(169, 101, 255);
+ background-color: rgb(230, 229, 235);
+ padding: 5px 10px;
+ color: rgb(0, 0, 0);
+}
+
+QWidget#titlebar {
+ background-color: rgb(119, 25, 170);
+}
+
+QWidget#titlebar QToolButton,
+QWidget#titlebar QWidget,
+QWidget#searchbarWidget {
+ background-color: rgb(119, 25, 170);
+}
+
+QWidget#searchbarWidget QLineEdit {
+ border-radius: 3px;
+ background-color: rgb(225,209,235);
+ color: rgb(119, 25, 170);
+ height: 60px;
+}
+
+QWidget#searchbarWidget QLineEdit::hover,
+QWidget#searchbarWidget QLineEdit::focus {
+ background-color: white;
+}
+
+QToolButton#tray::hover,
+QToolButton#windowed::hover,
+QToolButton#fullscreen::hover {
+ background-color: rgb(108, 23, 154);
+}
+
+QToolButton#close::hover {
+ background-color: rgb(232, 17, 35);
}
QMenuBar::item:selected {
@@ -15,7 +53,9 @@ QMenuBar::item:selected {
}
QToolBar {
- margin: 10px, 10px, 0, 0;
+ border-radius: 8px;
+ spacing: 10px;
+ background-color: white;
}
QComboBox {
@@ -32,7 +72,8 @@ QPushButton#font_color {
height: 20px;
}
-QToolButton::hover, QPushButton#font_color::hover {
+QToolBar QToolButton::hover,
+QToolBar QPushButton::hover {
background-color: #cecece;
}
@@ -40,34 +81,52 @@ QToolButton::checked {
background-color: #b1b1b1;
}
-QToolButton::checked::hover {
+QToolBar QToolButton::checked::hover {
background-color: #9b9b9b;
}
QTreeView {
- background-color: #c2c2c2;
+ background-color: rgb(230, 229, 235);
}
QPushButton {
- padding: 5px, 5px, 0, 0;
+ padding: 5px 0;
}
QLabel#notebook_title {
- padding: 5px, 5px, 0, 0;
+ padding: 5px;
text-align: center;
background-color: #d9d9d9;
}
+QLabel#notebook_title::hover {
+ padding: 5px;
+ text-align: center;
+ border-radius: 3px;
+ background-color: white;
+}
+
QLabel#pages_title {
- padding: 5px, 5px, 5px, 0;
+ padding: 5px;
background-color: #c2c2c2;
}
+QLabel#pages_title::hover {
+ padding: 5px;
+ border-radius: 3px;
+ background-color: white;
+}
+
QPushButton#addPage {
padding-bottom: 5px;
background-color: #d9d9d9;
}
QPushButton#addPage::hover {
- background-color: #cecece;
+ background-color: red;
}
+
+QTextEdit {
+ selection-background-color: #F0F0F0;
+ selection-color: #000000
+}
\ No newline at end of file
diff --git a/Styles/stylesDark.qss b/Styles/stylesDark.qss
new file mode 100644
index 0000000..07765b1
--- /dev/null
+++ b/Styles/stylesDark.qss
@@ -0,0 +1,73 @@
+* {
+ border: none;
+ padding: 0;
+ margin: 0;
+ font-family: "Segoe UI";
+ font-size: 16pt;
+}
+
+QMenuBar {
+ background-color: rgb(40, 20, 60);
+}
+
+QMenuBar::item:selected {
+ background-color: rgb(60, 20, 60);
+}
+
+QToolBar {
+ margin: 10px, 10px, 0, 0;
+}
+
+QComboBox {
+ width: 50px;
+}
+
+QToolButton {
+ width: 25px;
+ height: 25px;
+}
+
+QPushButton#font_color {
+ width: 30px;
+ height: 20px;
+}
+
+QToolButton::hover, QPushButton#font_color::hover {
+ background-color: #393939;
+}
+
+QToolButton::checked {
+ background-color: #272727;
+}
+
+QToolButton::checked::hover {
+ background-color: #1f1f1f;
+}
+
+QTreeView {
+ background-color: #292929;
+}
+
+QPushButton {
+ padding: 5px, 5px, 0, 0;
+}
+
+QLabel#notebook_title {
+ padding: 5px, 5px, 0, 0;
+ text-align: center;
+ background-color: #333333;
+}
+
+QLabel#pages_title {
+ padding: 5px, 5px, 5px, 0;
+ background-color: #292929;
+}
+
+QPushButton#addPage {
+ padding-bottom: 5px;
+ background-color: #333333;
+}
+
+QPushButton#addPage::hover {
+ background-color: #393939;
+}
diff --git a/Views/EditorFrameView.py b/Views/EditorFrameView.py
index 5232ead..9e08527 100644
--- a/Views/EditorFrameView.py
+++ b/Views/EditorFrameView.py
@@ -7,23 +7,56 @@
import sys
from Modules.Multiselect import Multiselector, MultiselectMode
+from Modules.Clipboard import Clipboard
+from Modules.EditorSignals import editorSignalsInstance, ChangedWidgetAttribute
+from Modules.Undo import UndoHandler
+from Modules.Screensnip import SnippingWidget
from Models.DraggableContainer import DraggableContainer
from Widgets.Textbox import TextboxWidget
-from Modules.EditorSignals import editorSignalsInstance
from Widgets.Image import ImageWidget
-from Modules.Screensnip import SnippingWidget
-from Widgets.Table import TableWidget
-from Modules.Clipboard import Clipboard
-from Modules.Undo import UndoHandler
+from Widgets.Table import *
+from Widgets.Link import LinkDialog
+
+import subprocess
+
+
# Handles all widget display (could be called widget view, but so could draggablecontainer)
class EditorFrameView(QWidget):
+ SETTINGS_KEY = "BackgroundColor" # Key for saving the background color setting
+
def __init__(self, editor):
super(EditorFrameView, self).__init__()
+
+ def check_appearance():
+ """Checks DARK/LIGHT mode of macos."""
+ cmd = 'defaults read -g AppleInterfaceStyle'
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True)
+ return bool(p.communicate()[0])
+
+ # Reference to the main editor window
self.editor = editor # Store reference to the editor (QMainWindow)
self.editorFrame = QFrame(editor)
- self.editorFrame.setStyleSheet("background-color: white;")
+
+ #Default
+ self.currentBackgroundColor = self.loadBackgroundColor() or QColor(255, 255, 255)
+
+ is_dark_mode = check_appearance()
+ white = QColor("white")
+
+ #background page color
+ if is_dark_mode and (self.currentBackgroundColor == white or self.currentBackgroundColor == QColor(255, 255, 255)):
+ self.setStyleSheet(f"background-color: rgb(31, 31, 30);")
+ print("In dark mode, use dark mode color because the background is white or pciked white")
+ elif not is_dark_mode:
+ self.setStyleSheet(f"background-color: {self.currentBackgroundColor.name()};")
+ print("Set background color based on saved color in light mode")
+ else:
+ self.setStyleSheet(f"background-color: {self.currentBackgroundColor.name()};")
+ self.saveBackgroundColor()
+ print("Saving non-white color as the current background color")
# Layout for the editor frame
layout = QVBoxLayout(self)
@@ -43,16 +76,21 @@ def __init__(self, editor):
self.installEventFilter(self.multiselector)
# Undo setup
- self.shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
- self.shortcut.setContext(Qt.ApplicationShortcut)
- self.shortcut.activated.connect(self.undoHandler.undo)
- self.undoHandler.undoWidgetDelete.connect(self.undoWidgetDeleteEvent)
+ #self.shortcut = QShortcut(QKeySequence("Ctrl+Z"), self)
+ #self.shortcut.setContext(Qt.ApplicationShortcut)
+ #self.shortcut.activated.connect(self.triggerUndo)
+
+ print("BUILT FRAMEVIEW")
- print("BUILT FRAMEVIEW")
+ def triggerUndo(self):
+ print("triggerUndo Called")
+ self.undoHandler.undo
+ self.undoHandler.undoWidgetDelete.connect(self.undoWidgetDeleteEvent)
def pasteWidget(self, clickPos):
widgetOnClipboard = self.clipboard.getWidgetToPaste()
+ # Create draggable container for pasted widget
dc = DraggableContainer(widgetOnClipboard, self)
self.undoHandler.pushCreate(dc)
editorSignalsInstance.widgetAdded.emit(dc) # Notify section that widget was added
@@ -63,10 +101,12 @@ def pasteWidget(self, clickPos):
def snipScreen(self, clickPos):
def onSnippingCompleted(imageMatrix): # Called after screensnipper gets image
self.editor.setWindowState(Qt.WindowActive)
+ self.editor.setWindowFlags(Qt.WindowStaysOnTopHint)
self.editor.showMaximized()
if imageMatrix is None:
return
+ #Create image widget from the captured image
widgetModel = ImageWidget.newFromMatrix(clickPos, imageMatrix)
dc = DraggableContainer(widgetModel, self)
self.undoHandler.pushCreate(dc)
@@ -118,6 +158,9 @@ def removeWidgetEvent(self, draggableContainer):
def cutWidgetEvent(self, draggableContainer):
editorSignalsInstance.widgetCopied.emit(draggableContainer)
editorSignalsInstance.widgetRemoved.emit(draggableContainer)
+
+ def copyWidgetEvent(self, draggableContainer):
+ editorSignalsInstance.widgetCopied.emit(draggableContainer)
# Loading a preexisting (saved) widget into the frame inside a DraggableContainer
# Then add that DC instance reference to the sectionModel's widgets[] for runtime
@@ -149,39 +192,153 @@ def mouseReleaseEvent(self, event):
# Releasing the mouse after clicking to add text
else:
+ print("CREATE DRAGGABLE CONTAINER")
self.newWidgetOnSection(TextboxWidget, event.pos())
def mousePressEvent(self, event):
print("EDITORFRAME MOUSEPRESS")
editor = self.editor
+ #calls textwidget's clearSelectionSignal
+ if event.button() == Qt.LeftButton:
+ if self.rect().contains(event.pos()):
+ editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.LoseFocus, None)
+ super().mousePressEvent(event)
+
# Open context menu on right click
if event.buttons() == Qt.RightButton:
frame_menu = QMenu(self)
-
- add_image = QAction("Add Image", self)
- add_image.triggered.connect(lambda: self.newWidgetOnSection(ImageWidget, event.pos()))
- frame_menu.addAction(add_image)
-
- add_table = QAction("Add Table", editor)
- add_table.triggered.connect(lambda: self.newWidgetOnSection(TableWidget, event.pos()))
- frame_menu.addAction(add_table)
+ # frame_menu.setStyleSheet("font-size: 11pt;")
paste = QAction("Paste", editor)
paste.triggered.connect(lambda: self.pasteWidget(event.pos()))
- frame_menu.addAction(paste)
+
+ insert_Link = QAction("Insert Link", editor)
+ insert_Link.triggered.connect(lambda: self.insertLink(event.pos()))
+ add_table = QAction("Add Table", editor)
+ add_table.triggered.connect(lambda: self.newWidgetOnSection(TableWidget, event.pos()))
+ #add_table.triggered.connect(self.show_table_popup)
+
+ # not necessary
+ '''
+ add_image = QAction("Add Image", self)
+ add_image.triggered.connect(lambda: self.newWidgetOnSection(ImageWidget, event.pos()))
+
take_screensnip = QAction("Snip Screen", editor)
take_screensnip.triggered.connect(lambda: self.snipScreen(event.pos()))
- frame_menu.addAction(take_screensnip)
add_custom_widget = QAction("Add Custom Widget", editor)
add_custom_widget.triggered.connect(lambda: self.addCustomWidget(event))
- frame_menu.addAction(add_custom_widget)
-
+ '''
+
+ frame_menu.addAction(paste)
+
+ frame_menu.addSeparator()
+
+ frame_menu.addAction(insert_Link)
+
+ frame_menu.addSeparator()
+
+ frame_menu.addAction(add_table)
+
+ '''
+ frame_menu.addSeparator()
+
+ frame_menu.addActions([add_image, take_screensnip, add_custom_widget])
+ '''
+
frame_menu.exec(event.globalPos())
- def addCustomWidget(self, event):
+ def insertLink(self, clickPos):
+ link_dialog = LinkDialog()
+ result = link_dialog.exec_() #Execute the dialog and wait for user input
+ if result == QDialog.Accepted:
+
+ link_address, display_text = link_dialog.get_link_data() # Get the link address and display text from the dialog
+ textboxWidget = TextboxWidget.new(clickPos) # Create a new TextboxWidget at the specified position
+ textboxWidget.insertTextLink(link_address, display_text) # Insert the hyperlink into the TextboxWidget
+
+ # Create a DraggableContainer for the TextboxWidget and show it
+ dc = DraggableContainer(textboxWidget, self)
+ dc.show()
+
+ self.undoHandler.pushCreate(dc)
+ editorSignalsInstance.widgetAdded.emit(dc)
+ editorSignalsInstance.changeMade.emit()
+
+ def insertDate(self, clickPos):
+ current_date = QDateTime.currentDateTime().toString("M/d/yyyy")
+ textboxWidget = TextboxWidget.new(clickPos)
+ textboxWidget.insertPlainText(current_date)
+ dc = DraggableContainer(textboxWidget, self)
+ dc.show()
+ self.undoHandler.pushCreate(dc)
+ editorSignalsInstance.widgetAdded.emit(dc)
+ editorSignalsInstance.changeMade.emit()
+
+ def insertTime(self, clickPos):
+ current_time = QDateTime.currentDateTime().toString("h:mm AP")
+ textboxWidget = TextboxWidget.new(clickPos)
+ textboxWidget.insertPlainText(current_time)
+ dc = DraggableContainer(textboxWidget, self)
+ dc.show()
+ self.undoHandler.pushCreate(dc)
+ editorSignalsInstance.widgetAdded.emit(dc)
+ editorSignalsInstance.changeMade.emit()
+
+ def center_of_screen(self):
+ editor_frame_geometry = self.editorFrame.geometry()
+ print(f"editor_frame_geometry.width() is {editor_frame_geometry.width()}")
+ print(f"editor_frame_geometry.height() is {editor_frame_geometry.height()}")
+ center_x = (editor_frame_geometry.width() - 200) // 2
+ center_y = (editor_frame_geometry.height() - 200) // 2
+ return center_x, center_y
+
+ # Used for calling functions in toolbar
+ def toolbar_paste(self):
+ print("toolbar_paste pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.pasteWidget(clickPos)
+
+ def toolbar_table(self):
+ print("toolbar_table pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.newWidgetOnSection(TableWidget, clickPos)
+
+ def toolbar_snipScreen(self):
+ print("toolbar_snipScreen pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.snipScreen(clickPos)
+
+ def toolbar_pictures(self):
+ print("toolbar_pictures pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.newWidgetOnSection(ImageWidget, clickPos)
+
+ def toolbar_hyperlink(self):
+ print("toolbar_hyperlink pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.insertLink(clickPos)
+
+ def toolbar_date(self):
+ print("toolbar_date pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.insertDate(clickPos)
+
+ def toolbar_time(self):
+ print("toolbar_time pressed")
+ center_x, center_y = self.center_of_screen()
+ clickPos = QPoint(center_x, center_y)
+ self.insertTime(clickPos)
+
+ def addCustomWidget(self, e):
def getCustomWidgets():
customWidgets = {} # dict where entries are {name: class}
@@ -206,17 +363,45 @@ def getCustomWidgets():
item_action = QAction(customWidget[0], self)
def tmp(c, pos):
return lambda: self.newWidgetOnSection(c, pos)
- item_action.triggered.connect(tmp(customWidget[1], event.pos()))
+ item_action.triggered.connect(tmp(customWidget[1], e.pos()))
pluginMenu.addAction(item_action)
- pluginMenu.exec(event.globalPos())
-
- def mouseMoveEvent(self, event): # This event is only called after clicking down on the frame and dragging
+ pluginMenu.exec(e.globalPos())
+ def mouseMoveEvent(self, e): # This event is only called after clicking down on the frame and dragging
# Set up multi-select on first move of mouse drag
if self.multiselector.mode != MultiselectMode.IS_DRAWING_AREA:
- self.multiselector.beginDrawingArea(event)
+ self.multiselector.beginDrawingArea(e)
# Resize multi-select widget on mouse every proceeding mouse movement (dragging)
else:
- self.multiselector.continueDrawingArea(event)
+ self.multiselector.continueDrawingArea(e)
+
+ def slot_action1(self, item):
+ print("Action 1 triggered")
+
+ # Handles changes to the background color of the editor frame.
+ def pageColor(self, color: QColor):
+ print("CHANGE BACKGROUND COLOR EVENT")
+ if color.isValid():
+ self.currentBackgroundColor = color
+ self.editorFrame.setStyleSheet(f"background-color: {color.name()};")
+ self.saveBackgroundColor()
+
+ #Loads the previously saved background color from settings
+ def loadBackgroundColor(self):
+ settings = QSettings()
+ color = settings.value(self.SETTINGS_KEY, type=QColor)
+ return color
+
+ # Saves the current background color to settings
+ def saveBackgroundColor(self):
+ settings = QSettings()
+ settings.setValue(self.SETTINGS_KEY, self.currentBackgroundColor)
+
+ # Retrieves the current background color of the editor frame
+ def getCurrentBackgroundColor(self):
+ return self.currentBackgroundColor
+
+
+
diff --git a/Views/NotebookTitleView.py b/Views/NotebookTitleView.py
index ca8dbb1..5faadfc 100644
--- a/Views/NotebookTitleView.py
+++ b/Views/NotebookTitleView.py
@@ -9,6 +9,7 @@ def __init__(self, notebookTitle: str):
self.notebookTitle = notebookTitle # Reference to the title on the notebook model
+ # Creating the QTextEdit widget for displaying and editing the title
self.titleWidget = QTextEdit()
self.titleWidget.setText(self.notebookTitle)
self.titleWidget.setFixedHeight(30)
diff --git a/Views/PageView.py b/Views/PageView.py
index 65922c6..427d3de 100644
--- a/Views/PageView.py
+++ b/Views/PageView.py
@@ -11,6 +11,9 @@
# Page view and controller
class PageView(QWidget):
+ # Class variable to keep track of page count
+ pageCount = 1
+
def __init__(self, pageModels: List[PageModel]):
super(PageView, self).__init__()
@@ -57,7 +60,7 @@ def loadPages(self, pageModels: List[PageModel]):
root.appendRow([rootPage])
# Create a first page
- newPageModel = PageModel('New Page', 0)
+ newPageModel = PageModel(f'New Page {self.pageCount}', 0)
newPage = QStandardItem(newPageModel.title)
newPage.setData(newPageModel)
newPage.setEditable(False)
@@ -123,15 +126,31 @@ def openMenu(self, position: QModelIndex):
level = 0
menu = QMenu()
- addChildAction = menu.addAction(self.tr("Add Page"))
- addChildAction.triggered.connect(partial(self.addPage, level, clickedIndex))
-
if not page.data().isRoot(): # Dont delete the root page
+ renamePageAction = menu.addAction(self.tr("Rename Page"))
+ renamePageAction.setIcon(QIcon('./Assets/icons/svg_rename'))
+ renamePageAction.triggered.connect(partial(self.renamePage, page))
+
deletePageAction = menu.addAction(self.tr("Delete Page"))
+ deletePageAction.setIcon(QIcon('./Assets/icons/svg_delete'))
deletePageAction.triggered.connect(partial(self.deletePage, page))
- renamePageAction = menu.addAction(self.tr("Rename Page"))
- renamePageAction.triggered.connect(partial(self.renamePage, page))
+ addChildAction = menu.addAction(self.tr("New Page"))
+ addChildAction.setIcon(QIcon('./Assets/icons/svg_add_page'))
+ addChildAction.triggered.connect(partial(self.addPage, level, clickedIndex))
+
+
+ #Current Issue: settings do not save because autosave only saves editor state
+ '''
+ mergePageAction = menu.addAction(self.tr("Merge Pages"))
+ mergePageAction.triggered.connect(partial(self.mergePages, page))
+ '''
+ # Why even have this???
+ # changeTextColorAction = menu.addAction(self.tr("Change Text Color"))
+ # changeTextColorAction.triggered.connect(partial(self.changeTextColor, page))
+
+ PageColorAction = menu.addAction(self.tr("Page Color"))
+ PageColorAction.triggered.connect(partial(self.PageColor, page))
menu.exec_(self.sender().viewport().mapToGlobal(position))
# Dont let the right click reach the viewport, context menu will still open but this will stop the page from being selected
@@ -140,22 +159,28 @@ def eventFilter(self, source, event):
if(event.button() == Qt.RightButton):
return True
return False
-
+
def addPage(self, level: int, clickedIndex: QModelIndex):
- # New page added under parent (what the user right clicked on)
+
+ # New page added to root
+ # will add functionallity for page groups which can be nested
+
parentPage = self.model.itemFromIndex(clickedIndex)
+ while not parentPage.data().isRoot():
+ parentPage = parentPage.parent()
parentPageUUID = parentPage.data().getUUID()
# Create a new page model, set that as the data for the new page
- newPageModel = PageModel('New Page', parentPageUUID)
+ self.pageCount += 1
+ newPageModel = PageModel(f'New Page {self.pageCount}', parentPageUUID)
newPage = QStandardItem(newPageModel.title)
newPage.setData(newPageModel)
newPage.setEditable(False)
parentPage.appendRow([newPage]) # Add to UI
self.pageModels.append(newPageModel) # Add to array of PageModel
- self.tree.expand(clickedIndex)
+ self.tree.expand(clickedIndex)
def deletePage(self, page: QStandardItem):
deletePages = [page]
@@ -209,3 +234,69 @@ def changePage(self, current: QModelIndex, previous: QModelIndex):
print("CHANGED PAGE TO: " + newPage.data().title)
editorSignalsInstance.pageChanged.emit(newPageModel) # Tell the sectionView that the page has changed
+
+
+ # Not sure if working as intended
+ '''
+ def mergePages(self, page: QStandardItem):
+ selectedIndexes = self.tree.selectedIndexes()
+
+ if len(selectedIndexes) == 2:
+ page1Item = self.model.itemFromIndex(selectedIndexes[0])
+ page2Item = self.model.itemFromIndex(selectedIndexes[1])
+
+ # Extract the PageModel objects from the selected QStandardItem objects
+ page1Model = page1Item.data()
+ page2Model = page2Item.data()
+
+ # Prompt the user for the name of the page to merge into the second page
+ name, ok = QInputDialog.getText(self, 'Merge Pages', f'Enter the name of the page to merge into "{page2Model.title}":')
+ if not ok:
+ return
+
+ if name != page1Model.title:
+ QMessageBox.warning(self, 'Invalid Page Name', 'The entered page name does not match the selected page.', QMessageBox.Ok)
+ return
+
+ # Confirm the merge operation
+ reply = QMessageBox.question(self, 'Confirm Merge', 'This action cannot be undone. Are you sure you want to merge the pages?', QMessageBox.Yes | QMessageBox.No, QMessageBox.No)
+ if reply == QMessageBox.No:
+ return
+
+ # Merge the sections of the two pages into one page
+ mergedSections = page1Model.sections + page2Model.sections
+ newPageModel = PageModel(page2Model.title, page2Model.getParentUUID(), mergedSections)
+
+ # Create a new QStandardItem for the merged page
+ newPageItem = QStandardItem(newPageModel.title)
+ newPageItem.setData(newPageModel)
+ newPageItem.setEditable(False)
+
+ # Insert the new merged page at the bottom of the children of the root
+ root = self.model.invisibleRootItem()
+ index = root.rowCount() # Get the last index
+ root.insertRow(index, [newPageItem])
+
+ # Remove the original two pages from the model
+ root.removeRow(page1Item.row())
+ root.removeRow(page2Item.row())
+
+ # Update the pageModels list
+ self.pageModels.remove(page1Model)
+ self.pageModels.remove(page2Model)
+ self.pageModels.append(newPageModel)
+
+ # Expand the tree to show the changes
+ self.tree.expandAll()
+ else:
+ QMessageBox.warning(self, 'Invalid Selection', 'Please select exactly two pages to merge.', QMessageBox.Ok)
+ '''
+ def changeTextColor(self, page: QStandardItem):
+ color = QColorDialog.getColor()
+ if color.isValid():
+ page.setForeground(color)
+
+ def PageColor(self, page: QStandardItem):
+ color = QColorDialog.getColor()
+ if color.isValid():
+ page.setBackground(color)
\ No newline at end of file
diff --git a/Views/SectionView.py b/Views/SectionView.py
index b26d198..eb6eb24 100644
--- a/Views/SectionView.py
+++ b/Views/SectionView.py
@@ -98,14 +98,17 @@ def openMenu(self, position: QPoint):
sectionModel = self.tabs.tabData(clickedSectionIndex)
menu = QMenu()
- addSectionAction = menu.addAction(self.tr("Add Section"))
- addSectionAction.triggered.connect(partial(self.addSection, sectionModel, clickedSectionIndex))
+ renameSectionAction = menu.addAction(self.tr("Rename Section"))
+ renameSectionAction.setIcon(QIcon('./Assets/icons/svg_rename'))
+ renameSectionAction.triggered.connect(partial(self.renameSection, sectionModel, clickedSectionIndex))
deleteSectionAction = menu.addAction(self.tr("Delete Section"))
+ deleteSectionAction.setIcon(QIcon('./Assets/icons/svg_delete'))
deleteSectionAction.triggered.connect(partial(self.deleteSection, sectionModel, clickedSectionIndex))
- renameSectionAction = menu.addAction(self.tr("Rename Section"))
- renameSectionAction.triggered.connect(partial(self.renameSection, sectionModel, clickedSectionIndex))
+ addSectionAction = menu.addAction(self.tr("Add Section"))
+ addSectionAction.setIcon(QIcon('./Assets/icons/svg_add_page'))
+ addSectionAction.triggered.connect(partial(self.addSection, sectionModel, clickedSectionIndex))
menu.exec(self.tabs.mapToGlobal(position))
diff --git a/Widgets/Image.py b/Widgets/Image.py
index 64971e1..04b159d 100644
--- a/Widgets/Image.py
+++ b/Widgets/Image.py
@@ -1,6 +1,8 @@
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
+from PySide6.QtWidgets import QFileDialog
+
from Modules.Enums import WidgetType
@@ -19,17 +21,17 @@ def __init__(self, x, y, w, h, image_matrix):
bytes_per_line = 3 * matrix_width
q_image = QImage(image_matrix.data, matrix_width, matrix_height, bytes_per_line, QImage.Format_BGR888)
self.q_pixmap = QPixmap(q_image)
- self.setPixmap(self.q_pixmap.scaled(w, h, Qt.KeepAspectRatio, Qt.SmoothTransformation)) # Scale to widget geometry
+ self.setPixmap(self.q_pixmap.scaled(w, h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation)) # Scale to widget geometry
self.setGeometry(x, y, w, h) # this should get fixed
- self.persistantGeometry = self.geometry()
+ self.persistantGeometry = self.geometry()
# Handle resize
def newGeometryEvent(self, newGeometry):
new_w = newGeometry.width()
new_h = newGeometry.height()
if (self.w != new_w) or (self.h != new_h): # Not exactly sure how object's width and height attribute gets updated but this works
- self.setPixmap(self.q_pixmap.scaled(new_w, new_h, Qt.KeepAspectRatio, Qt.SmoothTransformation))
+ self.setPixmap(self.q_pixmap.scaled(new_w, new_h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
pixmap_rect = self.pixmap().rect()
w = pixmap_rect.width()
@@ -38,7 +40,7 @@ def newGeometryEvent(self, newGeometry):
self.persistantGeometry = newGeometry
- @staticmethod
+ '''@staticmethod
def new(clickPos):
# Get path from user
@@ -54,6 +56,33 @@ def new(clickPos):
image = ImageWidget(clickPos.x(), clickPos.y(), w, h, image_matrix) # Note: the editorframe will apply pos based on event
return image
+ '''
+ # uses inbuilt qt file dialog
+ @staticmethod
+ def new(clickPos):
+
+
+ # Create a dummy parent widget for the file dialog
+ dummy_parent = QWidget()
+
+ # Get path from user
+ options = QFileDialog.Options()
+ options |= QFileDialog.DontUseNativeDialog # Use Qt's built-in dialog instead of the native platform dialog
+ path, _ = QFileDialog.getOpenFileName(dummy_parent, 'Add Image', '', 'Images (*.png *.xpm *.jpg *.bmp *.jpeg);;All Files (*)', options=options)
+
+ # Check if the user selected a file
+ if path:
+ # Get image size
+ image_matrix = cv2.imread(path)
+ h, w, _ = image_matrix.shape
+
+ # Create image and add to notebook
+ image = ImageWidget(clickPos.x(), clickPos.y(), w, h, image_matrix)
+ return image
+
+ # Return None or handle the case where the user cancels the dialog
+ return None
+
@staticmethod # Special staticmethod that screensnip uses
def newFromMatrix(clickPos, imageMatrix):
@@ -70,3 +99,130 @@ def __getstate__(self):
def __setstate__(self, state):
self.__init__(state['geometry'].x(), state['geometry'].y(), state['geometry'].width(), state['geometry'].height(), state['image_matrix'])
+
+ def customMenuItems(self):
+ def build_action(parent, icon_path, action_name, set_status_tip, set_checkable):
+ action = QAction(QIcon(icon_path), action_name, parent)
+ action.setStatusTip(set_status_tip)
+ action.setCheckable(set_checkable)
+ return action
+
+
+ toolbarBottom = QToolBar()
+ toolbarBottom.setIconSize(QSize(16, 16))
+ toolbarBottom.setMovable(False)
+
+ #crop = build_action(toolbarTop, './Assets/icons/svg_crop', "Crop", "Crop", False)
+
+ flipHorizontal = build_action(toolbarBottom, './Assets/icons/svg_flip_horizontal', "Horizontal Flip", "Horizontal Flip", False)
+ flipHorizontal.triggered.connect(self.flipHorizontal)
+ flipVertical = build_action(toolbarBottom, './Assets/icons/svg_flip_vertical', "Vertical Flip", "Vertical Flip", False)
+ flipVertical.triggered.connect(self.flipVertical)
+
+ rotateLeftAction = build_action(toolbarBottom, './Assets/icons/svg_rotate_left', "Rotate 90 degrees Left", "Rotate 90 degrees Left", False)
+ rotateLeftAction.triggered.connect(self.rotate90Left)
+ rotateRightAction = build_action(toolbarBottom, './Assets/icons/svg_rotate_right', "Rotate 90 degrees Right", "Rotate 90 degrees Right", False)
+
+ rotateRightAction.triggered.connect(self.rotate90Right)
+
+ shrinkImageAction = build_action(toolbarBottom, 'Assets/icons/svg_shrink', "Shrink", "Shrink", False)
+ shrinkImageAction.triggered.connect(self.shrinkImage)
+ expandImageAction = build_action(toolbarBottom, 'Assets/icons/svg_expand', "Expand", "Expand", False)
+ expandImageAction.triggered.connect(self.expandImage)
+
+
+ toolbarBottom.addActions([rotateLeftAction, rotateRightAction, flipHorizontal, flipVertical, shrinkImageAction, expandImageAction])
+
+ qwaBottom = QWidgetAction(self)
+ qwaBottom.setDefaultWidget(toolbarBottom)
+
+ return [qwaBottom]
+
+ def flipVertical(self):
+ # Flip the image matrix vertically using OpenCV
+ parent_widget = self.parentWidget()
+ if parent_widget:
+ newX, newY = parent_widget.x(), parent_widget.y()
+ new_width, new_height = parent_widget.height(), parent_widget.width()
+ parent_widget.setGeometry(newX, newY, new_width, new_height)
+
+ self.image_matrix = cv2.flip(self.image_matrix, 0)
+ self.updatePixmap()
+
+
+ def flipHorizontal(self):
+ # Flip the image matrix horizontally using OpenCV
+
+ parent_widget = self.parentWidget()
+ if parent_widget:
+ newX, newY = parent_widget.x(), parent_widget.y()
+ new_width, new_height = parent_widget.height(), parent_widget.width()
+ parent_widget.setGeometry(newX, newY, new_width, new_height)
+
+ self.image_matrix = cv2.flip(self.image_matrix, 1)
+ self.updatePixmap()
+
+
+ def rotate90Left(self):
+ # Rotate the image matrix 90 degrees to the left using OpenCV
+ self.w, self.h = self.h, self.w
+
+ parent_widget = self.parentWidget()
+ if parent_widget:
+ newX, newY = parent_widget.x(), parent_widget.y()
+ new_width, new_height = parent_widget.height(), parent_widget.width()
+ parent_widget.setGeometry(newX, newY, new_width, new_height)
+ self.image_matrix = cv2.rotate(self.image_matrix, cv2.ROTATE_90_COUNTERCLOCKWISE)
+
+ self.updatePixmap()
+ def rotate90Right(self):
+ # Rotate the image matrix 90 degrees to the right using OpenCV
+ self.w, self.h = self.h, self.w
+
+ # Access parent and update geometry
+ parent_widget = self.parentWidget()
+ if parent_widget:
+ newX, newY = parent_widget.x(), parent_widget.y()
+ new_width, new_height = parent_widget.height(), parent_widget.width()
+ parent_widget.setGeometry(newX, newY, new_width, new_height)
+
+ self.image_matrix = cv2.rotate(self.image_matrix, cv2.ROTATE_90_CLOCKWISE)
+ self.updatePixmap()
+
+ def shrinkImage(self):
+ # Decrease image size by 10%
+ self.w = int(self.w * 0.9)
+ self.h = int(self.h * 0.9)
+ parent_widget = self.parentWidget()
+ if parent_widget:
+ newX, newY = parent_widget.x(), parent_widget.y()
+ new_width, new_height = self.w, self.h
+ parent_widget.setGeometry(newX, newY, new_width, new_height)
+ self.setPixmap(self.q_pixmap.scaled(self.w, self.h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
+
+ def expandImage(self):
+ # Increase image size by 10%
+ self.w = int(self.w * 1.1)
+ self.h = int(self.h * 1.1)
+ parent_widget = self.parentWidget()
+ if parent_widget:
+ newX, newY = parent_widget.x(), parent_widget.y()
+ new_width, new_height = self.w, self.h
+ parent_widget.setGeometry(newX, newY, new_width, new_height)
+ self.setPixmap(self.q_pixmap.scaled(self.w, self.h, Qt.IgnoreAspectRatio, Qt.SmoothTransformation))
+
+ # Updates display. Note: Keeps Aspect Ratio
+ def updateImageSize(self):
+ # Update the displayed pixmap with the new size
+ self.setPixmap(self.q_pixmap.scaled(self.w, self.h, Qt.KeepAspectRatio, Qt.SmoothTransformation))
+
+
+ def updatePixmap(self):
+ # Update the QImage and QPixmap
+ matrix_height, matrix_width, _ = self.image_matrix.shape
+ bytes_per_line = 3 * matrix_width
+ q_image = QImage(self.image_matrix.data, matrix_width, matrix_height, bytes_per_line, QImage.Format_BGR888)
+ self.q_pixmap = QPixmap(q_image)
+
+ # Update the displayed pixmap
+ self.setPixmap(self.q_pixmap.scaled(self.w, self.h, Qt.KeepAspectRatio, Qt.SmoothTransformation))
diff --git a/Widgets/Link.py b/Widgets/Link.py
new file mode 100644
index 0000000..7258efe
--- /dev/null
+++ b/Widgets/Link.py
@@ -0,0 +1,47 @@
+from PySide6.QtCore import *
+from PySide6.QtGui import *
+from PySide6.QtWidgets import *
+
+
+FONT_SIZES = [7, 8, 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288]
+class LinkDialog(QDialog):
+ def __init__(self):
+ super().__init__()
+ self.setWindowTitle("Insert Link")
+ layout = QVBoxLayout()
+
+ # to stay on top of application
+ self.setWindowFlag(Qt.WindowStaysOnTopHint)
+
+ self.link_label = QLabel("Link Address:")
+ self.link_textbox = QLineEdit()
+ self.display_label = QLabel("Display Text:")
+ self.display_textbox = QLineEdit()
+
+ layout.addWidget(self.link_label)
+ layout.addWidget(self.link_textbox)
+ layout.addWidget(self.display_label)
+ layout.addWidget(self.display_textbox)
+
+ ok_button = QPushButton("OK")
+ ok_button.clicked.connect(self.accept)
+ cancel_button = QPushButton("Cancel")
+ cancel_button.clicked.connect(self.reject)
+
+ layout.addWidget(ok_button)
+ layout.addWidget(cancel_button)
+
+ self.setLayout(layout)
+
+ self.setModal(False)
+
+ def get_link_data(self):
+ link_address = self.link_textbox.text()
+ display_text = self.display_textbox.text()
+ return link_address, display_text
+ def mousePressEvent(self, event):
+ # Check if the mouse click is outside the dialog
+ if event.button() == Qt.LeftButton and not self.rect().contains(event.globalPos()):
+ self.close()
+
+
diff --git a/Widgets/Table.py b/Widgets/Table.py
index 7f615ff..d3ad592 100644
--- a/Widgets/Table.py
+++ b/Widgets/Table.py
@@ -8,7 +8,8 @@ def __init__(self, x, y, w, h, rows, cols):
# The actual table widget
self.table = QTableWidget(rows, cols, self)
- # Hide the horizontal and vertical headers
+ # Hides the horizontal and vertical headers
+ # These actually look really cool tho
self.table.horizontalHeader().setVisible(False)
self.table.verticalHeader().setVisible(False)
@@ -42,17 +43,14 @@ def addCol(self):
@staticmethod
def new(clickPos: QPoint):
- return TableWidget(clickPos.x(), clickPos.y(), 200, 200, 2, 2)
-
- def customMenuItems(self):
- addRow = QAction("Add Row", self)
- addRow.triggered.connect(self.addRow)
-
- addCol = QAction("Add Column", self)
- addCol.triggered.connect(self.addCol)
-
- return [addRow, addCol]
-
+ dialog = TablePopupWindow()
+ if dialog.exec_() == QDialog.Accepted:
+ rows_input, cols_input = dialog.get_table_data()
+ print(f"rows input is {rows_input} cols_input is {cols_input}")
+ table_widget = TableWidget(clickPos.x(), clickPos.y(), 200, 200, int(rows_input), int(cols_input))
+
+ return table_widget
+
def __getstate__(self):
state = {}
@@ -79,3 +77,55 @@ def __setstate__(self, state):
for i in range(colCnt):
for j in range(rowCnt):
self.table.setItem(j, i, QTableWidgetItem(state['tableData'][i][j]))
+
+def show_table_popup(self):
+ popup = TablePopupWindow()
+ popup.exec_()
+ #def undo_triggered(self):
+ # Call the EditorFrameView's triggerUndo method
+ #self.EditorFrameView.triggerUndo()
+
+class TablePopupWindow(QDialog):
+ def __init__(self):
+ super().__init__()
+
+ # to stay on top of application
+ self.setWindowFlag(Qt.WindowStaysOnTopHint)
+
+ self.setWindowTitle("Table Configuration")
+ self.layout = QVBoxLayout()
+
+ self.rows_input = QLineEdit(self)
+ self.rows_input.setPlaceholderText("Enter number of rows:")
+ self.layout.addWidget(self.rows_input)
+
+ self.cols_input = QLineEdit(self)
+ colNum = self.cols_input.setPlaceholderText("Enter number of columns:")
+ self.layout.addWidget(self.cols_input)
+
+ create_table_button = QPushButton("Create Table")
+ self.layout.addWidget(create_table_button)
+ create_table_button.clicked.connect(self.accept)
+ #create error message if no data is entered or if number of rows or columns are < 1
+
+ cancel_button = QPushButton("Cancel")
+ self.layout.addWidget(cancel_button)
+ cancel_button.clicked.connect(self.reject)
+
+
+ self.setLayout(self.layout)
+
+ self.setModal(False)
+
+ def get_table_data(self):
+ rows_input = self.rows_input.text()
+ cols_input = self.cols_input.text()
+ return rows_input, cols_input
+
+ def create_table(self):
+ print("table")
+
+ def mousePressEvent(self, event):
+ # Check if the mouse click is outside the dialog
+ if event.button() == Qt.LeftButton and not self.rect().contains(event.globalPos()):
+ self.close()
diff --git a/Widgets/Textbox.py b/Widgets/Textbox.py
index 998d2fe..4312a7a 100644
--- a/Widgets/Textbox.py
+++ b/Widgets/Textbox.py
@@ -1,24 +1,90 @@
from PySide6.QtCore import *
from PySide6.QtGui import *
from PySide6.QtWidgets import *
+from Modules.EditorSignals import editorSignalsInstance, ChangedWidgetAttribute, CheckSignal
+
+import subprocess
FONT_SIZES = [7, 8, 9, 10, 11, 12, 13, 14, 18, 24, 36, 48, 64, 72, 96, 144, 288]
+
class TextboxWidget(QTextEdit):
- def __init__(self, x, y, w = 15, h = 30, t = ''):
+ def __init__(self, x, y, w=15, h=30, t=""):
super().__init__()
- self.setGeometry(x, y, w, h) # This sets geometry of DraggableObject
+ def check_appearance():
+ """Checks DARK/LIGHT mode of macos."""
+ cmd = 'defaults read -g AppleInterfaceStyle'
+ p = subprocess.Popen(cmd, stdout=subprocess.PIPE,
+ stderr=subprocess.PIPE, shell=True)
+ return bool(p.communicate()[0])
+
+ self.setGeometry(x, y, w, h) # This sets geometry of DraggableObject
self.setText(t)
+ # self.setStyleSheet("background-color: rgba(0, 0, 0, 0); selection-background-color: #FFFFFF;")
+
self.setHorizontalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.setVerticalScrollBarPolicy(Qt.ScrollBarAlwaysOff)
self.textChanged.connect(self.textChangedEvent)
- self.setStyleSheet('background-color: rgba(0, 0, 0, 0);')
+ editorSignalsInstance.widgetAttributeChanged.connect(self.widgetAttributeChanged)
+
+
+
+ if check_appearance() == True:
+ self.setStyleSheet("background-color: rgba(31,31,30,255);")
+ #self.changeBackgroundColorEvent(31, 31, 30)
+ self.setTextColor("white")
+ self.changeAllTextColors("white")
+ else:
+ self.setStyleSheet("background-color: rgba(0, 0, 0, 0);")
+ #self.changeBackgroundColorEvent(0,0,0)
+ self.setTextColor("black")
+ self.changeAllTextColors("black")
+
+ self.setTextInteractionFlags(Qt.TextEditorInteraction | Qt.TextBrowserInteraction)
+ self.installEventFilter(self)
+
+ def eventFilter(self, obj, event):
+ if event.type() == QEvent.KeyPress and event.key() == Qt.Key_Tab:
+ self.handleTabKey()
+ return True # To prevent the default Tab key behavior
+ '''if event.type() == QEvent.FocusOut:
+ if self.checkEmpty():
+ parent = self.parent()
+ if parent is not None:
+ parent.deleteLater()'''
+
+ return super(TextboxWidget, self).eventFilter(obj, event)
+
+ # upon clicking somewhere else, remove selection of highlighted text
+ def setCursorPosition(self, event):
+ print("SET TEXT CURSOR POSITION TO MOUSE POSITION")
+ cursor = self.cursorForPosition(event.pos())
+ self.setTextCursor(cursor)
+
+ # Initial size is w=15, h=30. once changes to textbox has been detected, change the size
def textChangedEvent(self):
+ width = 150
+ height = 50
if len(self.toPlainText()) < 2:
- self.resize(100, 100)
+ self.resize(width, height)
+ else:
+ # handle cases where text reaches past the textbox
+ document = self.document()
+ documentSize = document.size().toSize()
+ documentHeight = documentSize.height()
+ # the document height is the text height. This is to expand the widget size to match document height
+ if(height < documentHeight):
+ height = documentHeight
+ self.resize(width, height)
+ def focusOutEvent(self, event):
+ super().focusOutEvent(event)
+
+
+
+
@staticmethod
def new(clickPos: QPoint):
@@ -27,69 +93,200 @@ def new(clickPos: QPoint):
def __getstate__(self):
data = {}
- data['geometry'] = self.parentWidget().geometry()
- data['content'] = self.toHtml()
- data['stylesheet'] = self.styleSheet()
+ data["geometry"] = self.parentWidget().geometry()
+ data["content"] = self.toHtml()
+ data["stylesheet"] = self.styleSheet()
return data
def __setstate__(self, data):
- self.__init__(data['geometry'].x(), data['geometry'].y(), data['geometry'].width(), data['geometry'].height(), data['content'])
- self.setStyleSheet(data['stylesheet'])
+ self.__init__(
+ data["geometry"].x(),
+ data["geometry"].y(),
+ data["geometry"].width(),
+ data["geometry"].height(),
+ data["content"],
+ )
+ self.setStyleSheet(data["stylesheet"])
def checkEmpty(self):
if len(self.toPlainText()) < 1:
return True
return False
+
+ # Handles events from any toolbar button
+ def widgetAttributeChanged(self, changedWidgetAttribute, value):
+ # dictionary of toolbar functions
+ attribute_functions = {
+ # note: for functions with no value passed, lambda _ will allow it to pass with no value
+
+ # font functions
+ ChangedWidgetAttribute.FontSize: lambda val: self.changeFontSizeEvent(val),
+ ChangedWidgetAttribute.FontBold: lambda _: self.changeFontBoldEvent(),
+ ChangedWidgetAttribute.FontItalic: lambda _: self.changeFontItalicEvent(),
+ ChangedWidgetAttribute.FontUnderline: lambda _: self.changeFontUnderlineEvent(),
+ ChangedWidgetAttribute.Strikethrough: lambda _: self.setStrikeOut(),
+ ChangedWidgetAttribute.Font: lambda val: self.changeFontEvent(val),
+ ChangedWidgetAttribute.FontColor: lambda val: self.changeFontColorEvent(val),
+ ChangedWidgetAttribute.TextHighlightColor: lambda val: self.changeTextHighlightColorEvent(val),
+
+ # background color functions
+ ChangedWidgetAttribute.BackgroundColor: lambda val: self.changeBackgroundColorEvent(val),
+ ChangedWidgetAttribute.PaperColor: lambda val: self.paperColor(val), # not implemented yet
+
+ # Bullet list functions
+ ChangedWidgetAttribute.Bullet: lambda _: self.bullet_list("bulletReg"),
+ ChangedWidgetAttribute.Bullet_Num: lambda _: self.bullet_list("bulletNum"),
+ ChangedWidgetAttribute.BulletUA: lambda _: self.bullet_list("bulletUpperA"),
+ ChangedWidgetAttribute.BulletUR: lambda _: self.bullet_list("bulletUpperR"),
+
+ # Alignment functions
+ ChangedWidgetAttribute.AlignLeft: lambda _: self.changeAlignmentEvent("alignLeft"),
+ ChangedWidgetAttribute.AlignCenter: lambda _: self.changeAlignmentEvent("alignCenter"),
+ ChangedWidgetAttribute.AlignRight: lambda _: self.changeAlignmentEvent("alignRight")
+ }
+ # if current widget is in focus
+ if (self.hasFocus or self.parentWidget().hasFocus) and changedWidgetAttribute in attribute_functions:
+ #if self.hasFocus() and changedWidgetAttribute in attribute_functions:
+ print(f"{changedWidgetAttribute} {value}")
+ # Calls the function in the dictionary
+ attribute_functions[changedWidgetAttribute](value)
+ else:
+ # Handle invalid attribute or other cases
+ pass
def customMenuItems(self):
- def build_action(parent, icon_path, action_name, set_status_tip, set_checkable):
- action = QAction(QIcon(icon_path), action_name, parent)
- action.setStatusTip(set_status_tip)
- action.setCheckable(set_checkable)
- return action
-
- toolbarTop = QToolBar()
- toolbarTop.setIconSize(QSize(25, 25))
- toolbarTop.setMovable(False)
-
- toolbarBottom = QToolBar()
- toolbarBottom.setIconSize(QSize(25, 25))
- toolbarBottom.setMovable(False)
-
- font = QFontComboBox()
- font.currentFontChanged.connect(lambda x: self.setCurrentFontCustom(font.currentFont() if x else self.currentFont()))
-
- size = QComboBox()
- size.addItems([str(fs) for fs in FONT_SIZES])
- size.currentIndexChanged.connect(lambda x: self.setFontPointSizeCustom(FONT_SIZES[x] if x else self.fontPointSize()))
-
- bold = build_action(toolbarBottom, 'assets/icons/svg_font_bold', "Bold", "Bold", True)
- bold.toggled.connect(lambda x: self.setFontWeightCustom(700 if x else 500))
-
- italic = build_action(toolbarBottom, 'assets/icons/svg_font_italic', "Italic", "Italic", True)
- italic.toggled.connect(lambda x: self.setFontItalicCustom(True if x else False))
-
- underline = build_action(toolbarBottom, 'assets/icons/svg_font_underline', "Underline", "Underline", True)
- underline.toggled.connect(lambda x: self.setFontUnderlineCustom(True if x else False))
-
- fontColor = build_action(toolbarBottom, 'assets/icons/svg_font_color', "Font Color", "Font Color", False)
- fontColor.triggered.connect(lambda x: self.setTextColorCustom(QColorDialog.getColor()))
-
- bgColor = build_action(toolbarBottom, 'assets/icons/svg_font_bucket', "Text Box Color", "Text Box Color", False)
- bgColor.triggered.connect(lambda x: self.setBackgroundColor(QColorDialog.getColor()))
-
- toolbarTop.addWidget(font)
- toolbarTop.addWidget(size)
- toolbarBottom.addActions([bold, italic, underline, fontColor, bgColor])
- qwaTop = QWidgetAction(self)
- qwaTop.setDefaultWidget(toolbarTop)
- qwaBottom = QWidgetAction(self)
- qwaBottom.setDefaultWidget(toolbarBottom)
-
- return [qwaTop, qwaBottom]
+ def build_action(parent, icon_path, action_name, set_status_tip, set_checkable):
+ action = QAction(QIcon(icon_path), action_name, parent)
+ action.setStatusTip(set_status_tip)
+ action.setCheckable(set_checkable)
+ return action
+
+ toolbarTop = QToolBar()
+ toolbarTop.setIconSize(QSize(16, 16))
+ toolbarTop.setMovable(False)
+
+ toolbarBottom = QToolBar()
+ toolbarBottom.setIconSize(QSize(16, 16))
+ toolbarBottom.setMovable(False)
+
+ font = QFontComboBox()
+ font.setFixedWidth(150)
+ font.currentFontChanged.connect(
+ lambda x: self.setCurrentFontCustom(
+ font.currentFont() if x else self.currentFont()
+ )
+ )
+
+ size = QComboBox()
+ size.setFixedWidth(50)
+ size.addItems([str(fs) for fs in FONT_SIZES])
+ size.currentIndexChanged.connect(
+ lambda x: self.setFontPointSizeCustom(
+ FONT_SIZES[x] if x else self.fontPointSize()
+ )
+ )
+
+ align_left = build_action(toolbarBottom, "./Assets/icons/svg_align_left", "Align Left", "Align Left", False)
+ # align_left.triggered.connect(lambda x: self.setAlignment(Qt.AlignLeft))
+ align_left.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignLeft, None))
+
+
+ align_center = build_action(toolbarBottom, "./Assets/icons/svg_align_center", "Align Center", "Align Center", False)
+ # align_center.triggered.connect(lambda x: self.setAlignment(Qt.AlignCenter))
+ align_center.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignCenter, None))
+
+
+ align_right = build_action(toolbarBottom, "./Assets/icons/svg_align_right", "Align Right", "Align Right", False)
+ align_right.triggered.connect(lambda x: self.setAlignment(Qt.AlignRight))
+ align_right.triggered.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.AlignRight, None))
+
+
+ bold = build_action(
+ toolbarBottom, "./Assets/icons/svg_font_bold", "Bold", "Bold", True
+ )
+ bold.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontBold, None))
+
+ italic = build_action(toolbarBottom, "./Assets/icons/svg_font_italic", "Italic", "Italic", True)
+ italic.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontItalic, None))
+
+ underline = build_action(toolbarBottom,"./Assets/icons/svg_font_underline","Underline","Underline",True,)
+ underline.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontUnderline, None))
+
+ strikethrough = build_action(toolbarBottom,"./Assets/icons/svg_strikethrough", "Strikethrough", "Strikethrough", True)
+ strikethrough.toggled.connect(lambda: editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.Strikethrough, None))
+
+
+ fontColor = build_action(toolbarBottom,"./Assets/icons/svg_font_color","Font Color","Font Color",False)
+ fontColor.triggered.connect(
+ lambda: self.setTextColorCustom(QColorDialog.getColor())
+ )
+
+ bgColor = build_action(toolbarBottom,"./Assets/icons/svg_font_bucket","Background Color","Background Color",False)
+ # bgColor.triggered.connect(lambda: self.setBackgroundColor(QColorDialog.getColor()))
+ bgColor.triggered.connect(lambda: self.changeBackgroundColorEvent(QColorDialog.getColor()))
+ textHighlightColor = build_action(toolbarBottom,"./Assets/icons/svg_textHighlightColor","Text Highlight Color","Text Highlight Color",False,)
+ textHighlightColor.triggered.connect(lambda: self.changeTextHighlightColorEvent(QColorDialog.getColor()))
+
+ bullets = build_action(toolbarBottom, "./Assets/icons/svg_bullets", "Bullets", "Bullets", True)
+ bullets.toggled.connect(lambda: self.bullet_list("bulletReg"))
+
+ toolbarTop.addWidget(font)
+ toolbarTop.addWidget(size)
+
+ toolbarBottom.addActions(
+ [
+ bold,
+ italic,
+ underline,
+ # strikethrough,
+ textHighlightColor,
+ fontColor,
+ bullets
+ ]
+ )
+
+ # numbering menu has to be added inbetween
+ numbering_menu = QMenu(self)
+ bullets_num = numbering_menu.addAction(QIcon("./Assets/icons/svg_bullet_number"), "")
+ bulletUpperA = numbering_menu.addAction(QIcon("./Assets/icons/svg_bulletUA"), "")
+ bulletUpperR = numbering_menu.addAction(QIcon("./Assets/icons/svg_bulletUR"), "")
+
+ bullets_num.triggered.connect(lambda: self.bullet_list("bulletNum"))
+ bulletUpperA.triggered.connect(lambda: self.bullet_list("bulletUpperA"))
+ bulletUpperR.triggered.connect(lambda: self.bullet_list("bulletUpperR"))
+
+
+ numbering = QToolButton(self)
+ numbering.setIcon(QIcon("./Assets/icons/svg_bullet_number"))
+ numbering.setPopupMode(QToolButton.MenuButtonPopup)
+ numbering.setMenu(numbering_menu)
+
+ # This code would fix an error on the command line but it also makes it not look good soooo
+ numbering.setParent(numbering_menu)
+
+ toolbarBottom.addWidget(numbering)
+
+ # not required for right-click menu as they arent originally present in OneNote
+ '''
+ toolbarBottom.addActions(
+ [
+ bgColor,
+ align_left,
+ align_center,
+ align_right
+ ]
+ )
+ '''
+ qwaTop = QWidgetAction(self)
+ qwaTop.setDefaultWidget(toolbarTop)
+ qwaBottom = QWidgetAction(self)
+ qwaBottom.setDefaultWidget(toolbarBottom)
+
+ return [qwaTop, qwaBottom]
def setFontItalicCustom(self, italic: bool):
if not self.applyToAllIfNoSelection(lambda: self.setFontItalic(italic)):
+ print("setFontItalicCustom Called")
self.setFontItalic(italic)
def setFontWeightCustom(self, weight: int):
@@ -115,8 +312,7 @@ def setTextColorCustom(self, color):
def setBackgroundColor(self, color: QColor):
rgb = color.getRgb()
- self.setStyleSheet(f'background-color: rgb({rgb[0]}, {rgb[1]}, {rgb[2]});')
-
+ self.setStyleSheet(f"background-color: rgb({rgb[0]}, {rgb[1]}, {rgb[2]});")
# If no text is selected, apply to all, else apply to selection
def applyToAllIfNoSelection(self, func):
@@ -137,3 +333,347 @@ def applyToAllIfNoSelection(self, func):
cursor.clearSelection()
self.setTextCursor(cursor)
return True
+
+ def changeFontSizeEvent(self, weight):
+ print("changeFontSizeEvent Called")
+ self.setFontWeightCustom(weight)
+
+ # for communicating the signal editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontItalic, None)
+ # current issue for Event Functions: Only affects highlighted
+
+ def changeFontItalicEvent(self):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ # Checks if currently selected text is italics
+ is_italic = current_format.fontItalic()
+
+ # toggles the italics
+ current_format.setFontItalic(not is_italic)
+
+ # Apply modified format to selected text
+ cursor.setCharFormat(current_format)
+
+ # Update text cursor with modified format
+ self.setTextCursor(cursor)
+
+ def changeFontBoldEvent(self):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ # Checks if currently selected text is bold
+ is_bold = current_format.fontWeight() == 700
+
+ # toggles the italics
+ if is_bold:
+ current_format.setFontWeight(500)
+ else:
+ current_format.setFontWeight(700)
+ # Apply modified format to selected text
+ cursor.setCharFormat(current_format)
+
+ # Update text cursor with modified format
+ self.setTextCursor(cursor)
+
+ def changeFontUnderlineEvent(self):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ # Checks if currently selected text is bold
+ is_underlined = current_format.fontUnderline()
+
+ # toggles the underline
+ current_format.setFontUnderline(not is_underlined)
+
+ # Apply modified format to selected text
+ cursor.setCharFormat(current_format)
+
+ # Update text cursor with modified format
+ self.setTextCursor(cursor)
+
+ def bullet_list(self, bulletType):
+ cursor = self.textCursor()
+ textList = cursor.currentList()
+
+ if textList:
+ start = cursor.selectionStart()
+ end = cursor.selectionEnd()
+ removed = 0
+ for i in range(textList.count()):
+ item = textList.item(i - removed)
+ if item.position() <= end and item.position() + item.length() > start:
+ textList.remove(item)
+ blockCursor = QTextCursor(item)
+ blockFormat = blockCursor.blockFormat()
+ blockFormat.setIndent(0)
+ blockCursor.mergeBlockFormat(blockFormat)
+ removed += 1
+
+ cursor = self.textCursor()
+ cursor.setBlockFormat(QTextBlockFormat()) # Clear any previous block format
+
+ self.setTextCursor(cursor)
+ self.setFocus()
+
+ else:
+ listFormat = QTextListFormat()
+
+ if bulletType == "bulletNum":
+ style = QTextListFormat.ListDecimal
+ if bulletType == "bulletReg":
+ style = QTextListFormat.ListDisc
+ if bulletType == "bulletLowerA":
+ style = QTextListFormat.ListLowerAlpha
+ if bulletType == "bulletLowerR":
+ style = QTextListFormat.ListLowerRoman
+ if bulletType == "bulletUpperA":
+ style = QTextListFormat.ListUpperAlpha
+ if bulletType == "bulletUpperR":
+ style = QTextListFormat.ListUpperRoman
+
+ listFormat.setStyle(style)
+ cursor.createList(listFormat)
+
+ self.setTextCursor(cursor)
+ self.setFocus()
+ def changeAlignmentEvent(self, alignmentType):
+ print("Alignment Event Called")
+ cursor = self.textCursor()
+ blockFormat = cursor.blockFormat()
+
+ if alignmentType == "alignLeft":
+ blockFormat.setAlignment(Qt.AlignLeft)
+ elif alignmentType == "alignCenter":
+ blockFormat.setAlignment(Qt.AlignCenter)
+ elif alignmentType == "alignRight":
+ blockFormat.setAlignment(Qt.AlignRight)
+
+ cursor.setBlockFormat(blockFormat)
+ self.setTextCursor(cursor)
+ self.setFocus()
+
+ def handleTabKey(self):
+ cursor = self.textCursor()
+ block = cursor.block()
+ block_format = block.blockFormat()
+ textList = cursor.currentList()
+
+ if textList:
+ if cursor.atBlockStart() and block.text().strip() == "":
+ current_indent = block_format.indent()
+
+ if current_indent < 11:
+
+ if current_indent % 3 == 0:
+ block_format.setIndent(current_indent + 1)
+ cursor.setBlockFormat(block_format)
+ cursor.beginEditBlock()
+ list_format = QTextListFormat()
+ currentStyle = textList.format().style()
+
+ if currentStyle == QTextListFormat.ListDisc:
+ list_format.setStyle(QTextListFormat.ListCircle)
+ if currentStyle == QTextListFormat.ListDecimal:
+ list_format.setStyle(QTextListFormat.ListLowerAlpha)
+ if currentStyle == QTextListFormat.ListLowerAlpha:
+ list_format.setStyle(QTextListFormat.ListLowerRoman)
+ if currentStyle == QTextListFormat.ListLowerRoman:
+ list_format.setStyle(QTextListFormat.ListDecimal)
+ if currentStyle == QTextListFormat.ListUpperAlpha:
+ list_format.setStyle(QTextListFormat.ListLowerAlpha)
+ if currentStyle == QTextListFormat.ListUpperRoman:
+ list_format.setStyle(QTextListFormat.ListLowerAlpha)
+
+ cursor.createList(list_format)
+ cursor.endEditBlock()
+
+ if current_indent % 3 == 1:
+ block_format.setIndent(current_indent + 1)
+ cursor.setBlockFormat(block_format)
+ cursor.beginEditBlock()
+ list_format = QTextListFormat()
+ currentStyle = textList.format().style()
+
+ if currentStyle == QTextListFormat.ListCircle:
+ list_format.setStyle(QTextListFormat.ListSquare)
+ if currentStyle == QTextListFormat.ListLowerAlpha:
+ list_format.setStyle(QTextListFormat.ListLowerRoman)
+ if currentStyle == QTextListFormat.ListLowerRoman:
+ list_format.setStyle(QTextListFormat.ListDecimal)
+ if currentStyle == QTextListFormat.ListDecimal:
+ list_format.setStyle(QTextListFormat.ListLowerAlpha)
+
+ cursor.createList(list_format)
+ cursor.endEditBlock()
+
+ if current_indent % 3 == 2:
+ block_format.setIndent(current_indent + 1)
+ cursor.setBlockFormat(block_format)
+ cursor.beginEditBlock()
+ list_format = QTextListFormat()
+ currentStyle = textList.format().style()
+
+ if currentStyle == QTextListFormat.ListSquare:
+ list_format.setStyle(QTextListFormat.ListDisc)
+ if currentStyle == QTextListFormat.ListLowerRoman:
+ list_format.setStyle(QTextListFormat.ListDecimal)
+ if currentStyle == QTextListFormat.ListDecimal:
+ list_format.setStyle(QTextListFormat.ListLowerAlpha)
+ if currentStyle == QTextListFormat.ListLowerAlpha:
+ list_format.setStyle(QTextListFormat.ListLowerRoman)
+
+ cursor.createList(list_format)
+ cursor.endEditBlock()
+
+ cursor.insertText("")
+ cursor.movePosition(QTextCursor.StartOfBlock)
+ self.setTextCursor(cursor)
+
+ else:
+ cursor.insertText(" ")
+ self.setTextCursor(cursor)
+ pass
+
+ def insertTextLink(self, link_address, display_text):
+ self.setOpenExternalLinks(True)
+ link_html = f'{display_text}'
+ cursor = self.textCursor()
+ cursor.insertHtml(link_html)
+ QDesktopServices.openUrl(link_html)
+
+ def changeFontSizeEvent(self, value):
+ # todo: when textbox is in focus, font size on toolbar should match the font size of the text
+
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ current_format.setFontPointSize(value)
+ cursor.setCharFormat(current_format)
+
+ self.setTextCursor(cursor)
+
+ def changeFontEvent(self, font_style):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+ current_format.setFont(font_style)
+
+ cursor.setCharFormat(current_format)
+
+ self.setTextCursor(cursor)
+
+ # Changes font text color
+ def changeFontColorEvent(self, new_font_color):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ color = QColor(new_font_color)
+ if color.isValid():
+ current_format.setForeground(color)
+
+ cursor.setCharFormat(current_format)
+
+ # to not get stuck on highlighted text
+ # self.deselectText()
+ # self.setTextCursor(cursor)
+
+ # Changes color of whole background
+
+ def changeBackgroundColorEvent(self, color: QColor):
+ print("CHANGE BACKGROUND COLOR EVENT")
+
+ if color.isValid():
+ rgb = color.getRgb()
+ self.setStyleSheet(f"QTextEdit {{background-color: rgb({rgb[0]}, {rgb[1]}, {rgb[2]}); }}")
+
+ self.deselectText()
+
+ # Changes textbox background color
+ def changeTextHighlightColorEvent(self, new_highlight_color):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ color = QColor(new_highlight_color)
+ if color.isValid():
+ current_format.setBackground(color)
+
+ cursor.setCharFormat(current_format)
+ # self.deselectText()
+
+ # self.setTextCursor(cursor)
+
+ # Used to remove text highlighting
+ def deselectText(self):
+ cursor = self.textCursor()
+ cursor.clearSelection()
+ self.setTextCursor(cursor)
+
+ # Adds bullet list to text
+ def changeBulletEvent(self):
+ # put bullet function here
+ print("bullet press")
+
+ def changeAllTextColors(self, new_color):
+ cursor = self.textCursor()
+ cursor.movePosition(QTextCursor.Start)
+
+ while not cursor.atEnd():
+ cursor.movePosition(QTextCursor.NextCharacter, QTextCursor.KeepAnchor)
+ char_format = cursor.charFormat()
+
+ if char_format.foreground().color() == Qt.black or Qt.white:
+ char_format.setForeground(QColor(new_color))
+ cursor.setCharFormat(char_format)
+
+ cursor.movePosition(QTextCursor.Start)
+ self.setTextCursor(cursor)
+
+ def setStrikeOut(self):
+ print("strikeout event")
+ cursor = self.textCursor()
+ format = cursor.charFormat()
+
+ # Toggle strikethrough
+ format.setFontStrikeOut(not format.fontStrikeOut())
+
+ # Apply the new format to the selected text
+ #cursor.mergeCharFormat(format)
+ cursor.setCharFormat(format)
+
+ #cursor.movePosition(QTextCursor.End)
+ self.setTextCursor(cursor)
+ #self.setFocus()
+
+
+
+ def refactorTest(self):
+ cursor = self.textCursor()
+ current_format = cursor.charFormat()
+
+ # Checks if currently selected text is bold
+ is_bold = current_format.fontWeight() == 700
+
+ # toggles the italics
+ if is_bold:
+ current_format.setFontWeight(500)
+ else:
+ current_format.setFontWeight(700)
+ # Apply modified format to selected text
+ cursor.setCharFormat(current_format)
+ cursor.mergeCharFormat(format)
+ # Emit signal if no text is bold to toggle bold off
+ if not is_bold:
+ editorSignalsInstance.widgetAttributeChanged.emit(ChangedWidgetAttribute.FontBold, None)
+ # Update text cursor with modified format
+ self.setTextCursor(cursor)
+
+ def selectAllText(self):
+ print("Select All Text function from textwidget called")
+ cursor = self.textCursor()
+ cursor.setPosition(0)
+ cursor.movePosition(QTextCursor.End, QTextCursor.KeepAnchor)
+ self.setTextCursor(cursor)
+
+ def removeWidget(self):
+ if(self.checkEmpty()):
+ print("removing widget")
+ editorSignalsInstance.widgetRemoved.emit(self)
\ No newline at end of file
diff --git a/requirements.txt b/requirements.txt
index f573bd8..8c8e968 100644
Binary files a/requirements.txt and b/requirements.txt differ