|
| 1 | +from PySide6.QtWidgets import QMessageBox, QMenu, QInputDialog, QTextEdit, QWidget, QVBoxLayout |
| 2 | +from PySide6.QtCore import Qt |
| 3 | +from PySide6.QtGui import QAction |
| 4 | +from PySide6 import QtGui |
| 5 | +import markdown |
| 6 | + |
| 7 | +class EditActionsMixin: |
| 8 | + |
| 9 | + def init_ui(self): |
| 10 | + self.ui.statusBarMessage() |
| 11 | + self.ui.menu = self.create_menu() |
| 12 | + self.ui.splitter.setStyleSheet("QSplitter::handle {background-color:#dfe2e5; ...}") |
| 13 | + self.ui.editArea.setFocus() |
| 14 | + self.ui.previewArea.setZoomFactor(0.8) |
| 15 | + self.setGeometry(100, 100, 800, 600) |
| 16 | + self.setWindowTitle('DocViewer') |
| 17 | + self.ui.file_btn.clicked.connect(self.show_menu) |
| 18 | + self.ui.tab_widget.tabCloseRequested.connect(self.close_tab) |
| 19 | + self.ui.tab_widget.currentChanged.connect(self.onTabChange) |
| 20 | + self.ui.tab_widget.tabBar().setContextMenuPolicy(Qt.CustomContextMenu) |
| 21 | + self.ui.tab_widget.tabBar().customContextMenuRequested.connect(self.onTabRightClick) |
| 22 | + self.new_file() |
| 23 | + |
| 24 | + def create_menu(self): |
| 25 | + menu = QMenu() |
| 26 | + new_file_act = QAction("Novo arquivo", self) |
| 27 | + new_file_act.setShortcut("Ctrl+N") |
| 28 | + new_file_act.triggered.connect(self.new_file) |
| 29 | + menu.addAction(new_file_act) |
| 30 | + |
| 31 | + open_file_act = QAction("Abrir Arquivo", self) |
| 32 | + open_file_act.setShortcut("Ctrl+O") |
| 33 | + open_file_act.triggered.connect(self.showDialogAndOpenFile) |
| 34 | + menu.addAction(open_file_act) |
| 35 | + |
| 36 | + save_file_act = QAction("Salvar Arquivo", self) |
| 37 | + save_file_act.setShortcut("Ctrl+S") |
| 38 | + save_file_act.triggered.connect(self.saveFile) |
| 39 | + menu.addAction(save_file_act) |
| 40 | + |
| 41 | + menu.setFixedWidth(210) |
| 42 | + return menu |
| 43 | + |
| 44 | + def show_menu(self): |
| 45 | + if not self.ui.has_open_menu: |
| 46 | + self.ui.menu.exec(self.ui.file_btn.mapToGlobal(self.ui.file_btn.rect().bottomLeft())) |
| 47 | + self.ui.has_open_menu = True |
| 48 | + else: |
| 49 | + self.ui.menu.close() |
| 50 | + self.ui.has_open_menu = False |
| 51 | + |
| 52 | + def onTabRightClick(self, position): |
| 53 | + """Exibe um diálogo para renomear arquivo ao clicar com o botão direito em cima da aba""" |
| 54 | + tab_index = self.ui.tab_widget.tabBar().tabAt(position) |
| 55 | + tab_widget = self.ui.tab_widget.widget(tab_index) |
| 56 | + if tab_index != -1: |
| 57 | + # Cria o menu de contexto |
| 58 | + menu = QMenu(self) |
| 59 | + |
| 60 | + rename_action = QAction("Renomear arquivo", self) |
| 61 | + rename_action.triggered.connect(lambda: self.renameTab(tab_index, tab_widget)) |
| 62 | + |
| 63 | + open_action = QAction("Abrir aqui", self) |
| 64 | + open_action.triggered.connect(lambda: self.open_here(tab_index)) |
| 65 | + |
| 66 | + save_action = QAction("Salvar arquivo", self) |
| 67 | + save_action.triggered.connect(lambda: self.saveFile()) |
| 68 | + |
| 69 | + # Adiciona as ações ao menu |
| 70 | + menu.addAction(rename_action) |
| 71 | + menu.addAction(open_action) |
| 72 | + menu.addAction(save_action) |
| 73 | + #menu.addSeparator() # Adiciona um separador visual |
| 74 | + |
| 75 | + |
| 76 | + # Exibe o menu de contexto na posição do cursor |
| 77 | + menu.exec_(self.ui.tab_widget.tabBar().mapToGlobal(position)) |
| 78 | + # ... incluir updatePreview, inteliComplete, updateCompleteHtml, getMarkdownText, scroll_to_bottom etc |
| 79 | + def eventFilter(self, obj, event): |
| 80 | + if obj == self.ui.editArea and event.type() == QtCore.QEvent.KeyRelease and event.key() == QtCore.Qt.Key_Return: |
| 81 | + self.inteliComplete() |
| 82 | + return super().eventFilter(obj, event) |
| 83 | + |
| 84 | + def inteliComplete(self): |
| 85 | + cursor = self.ui.editArea.textCursor() |
| 86 | + cursor.movePosition(QtGui.QTextCursor.EndOfLine) # Move o cursor para o final da linha atual |
| 87 | + |
| 88 | + actual_line = cursor.block().text().strip() |
| 89 | + previous_line = cursor.block().previous().text().strip() # Obtém o texto da linha anterior |
| 90 | + #next_line = cursor.block().next().text().strip() |
| 91 | + #print("actual line", actual_line) |
| 92 | + if previous_line.startswith("-"): |
| 93 | + if(len(previous_line) > 2 and previous_line[2] == '['): |
| 94 | + cursor.insertText("- [ ] ") |
| 95 | + else: |
| 96 | + if(actual_line.startswith("-")): |
| 97 | + cursor.movePosition(QtGui.QTextCursor.EndOfLine) |
| 98 | + else: |
| 99 | + cursor.insertText("- ") |
| 100 | + elif previous_line.startswith(">"): |
| 101 | + cursor.insertText("> ") |
| 102 | + elif previous_line.startswith("_"): |
| 103 | + cursor.insertText("_ ") |
| 104 | + elif previous_line.startswith("*"): |
| 105 | + cursor.insertText("* ") |
| 106 | + elif previous_line.startswith("1."): |
| 107 | + cursor.insertText("1. ") |
| 108 | + elif previous_line.startswith("- "): |
| 109 | + cursor.insertText("- [ ] ") |
| 110 | + |
| 111 | + ##cursor.movePosition(QtGui.QTextCursor.StartOfLine, QtGui.QTextCursor.MoveAnchor) |
| 112 | + self.ui.editArea.setTextCursor(cursor) |
| 113 | + |
| 114 | + def getMarkdownText(self, input_text): |
| 115 | + mkd_text = markdown.markdown(input_text, extensions=['extra', 'tables','fenced_code', 'codehilite']) |
| 116 | + mkd_text = mkd_text.replace('[ ]', '<input type="checkbox" disabled>') # Necessário! |
| 117 | + mkd_text = mkd_text.replace('[x]', '<input type="checkbox" disabled checked>') # Necessário! |
| 118 | + return mkd_text |
| 119 | + |
| 120 | + def updatePreview(self, text_edit): |
| 121 | + """Atualiza a visualização com base no conteúdo do QTextEdit fornecido""" |
| 122 | + markdown_text = text_edit.toPlainText() |
| 123 | + self.verifyChangesAndSetTabName() |
| 124 | + |
| 125 | + self.html_text_ = self.getMarkdownText(markdown_text) |
| 126 | + self.updateCompleteHtml() |
| 127 | + self.ui.previewArea.setHtml(self.complete_html) |
| 128 | + ### TODO: Permitir ativar e desativar esta funcionalidade |
| 129 | + ##self.ui.previewArea.loadFinished.connect(self.scroll_to_bottom) |
| 130 | + |
| 131 | + def updateCompleteHtml(self): |
| 132 | + """Atualiza e redefine o conteúdo de complete_html""" |
| 133 | + self.complete_html = f""" |
| 134 | + <!DOCTYPE html> |
| 135 | + <html> |
| 136 | + <head> |
| 137 | + <meta charset="utf-8"> |
| 138 | + <style> |
| 139 | + body {{ |
| 140 | + font-family: -apple-system, BlinkMacSystemFont, "Segoe UI", Helvetica, Arial, sans-serif, "Apple Color Emoji", "Segoe UI Emoji"; |
| 141 | + line-height: 1.6; |
| 142 | + padding: 20px; |
| 143 | + color: #E6edf3; |
| 144 | + background-color: #161b22; |
| 145 | + }} |
| 146 | + h1, h2, h3, h4, h5, h6 {{ |
| 147 | + border-bottom: 1px solid #eaecef; |
| 148 | + padding-bottom: 0.3em; |
| 149 | + }} |
| 150 | + h1 {{ font-size: 2em; }} |
| 151 | + h2 {{ font-size: 1.5em; }} |
| 152 | + h3 {{ font-size: 1.25em; }} |
| 153 | + blockquote {{ |
| 154 | + color: #848d97; |
| 155 | + border-left: 0.25em solid #30363d; |
| 156 | + padding: 0.5em 1em; |
| 157 | + }} |
| 158 | + ol {{ |
| 159 | + padding-left: 20px; /* Recuo inicial da lista */ |
| 160 | + list-style-type: decimal; /* Números padrão para a lista */ |
| 161 | + font-family: Arial, sans-serif; /* Fonte padrão */ |
| 162 | + line-height: 1.6; /* Altura da linha para melhor legibilidade */ |
| 163 | + }} |
| 164 | + ol ol {{ |
| 165 | + padding-left: 20px; /* Recuo adicional para listas aninhadas */ |
| 166 | + list-style-type: lower-alpha; /* Letras minúsculas para segundo nível */ |
| 167 | + |
| 168 | + }} |
| 169 | +
|
| 170 | + ol ol ol {{ |
| 171 | + padding-left: 20px; /* Recuo adicional para listas de terceiro nível */ |
| 172 | + list-style-type: lower-roman; /* Números romanos minúsculos para terceiro nível */ |
| 173 | + |
| 174 | + }} |
| 175 | + code {{ |
| 176 | + font-family: ui-monospace, SFMono-Regular, SF Mono, Menlo, Consolas, "Liberation Mono", monospace; |
| 177 | + padding: .2em .4em; |
| 178 | + margin: 0; |
| 179 | + font-size: 85%; |
| 180 | + white-space: break-spaces; |
| 181 | + background-color: #afb8c133; |
| 182 | + border-radius: 6px; |
| 183 | + }} |
| 184 | + pre code {{ |
| 185 | + background-color: #30363d; |
| 186 | + padding: 0; |
| 187 | + font-size: 100%; |
| 188 | + }} |
| 189 | + pre {{ |
| 190 | + overflow: auto; |
| 191 | + overflow-x: auto; |
| 192 | + overflow-y: auto; |
| 193 | + background-color: #30363d; |
| 194 | + padding: 1em; |
| 195 | + overflow: auto; |
| 196 | + }} |
| 197 | + .codehilite .k {{ color: #f92672; }} /* Palavras-chave em rosa */ |
| 198 | + .codehilite .c {{ color: #75715e; font-style: italic; }} /* Comentários em cinza */ |
| 199 | + .codehilite .n {{ color: #a6e22e; }} /* Nomes de variáveis em verde */ |
| 200 | + .codehilite .s {{ color: #e6db74; }} |
| 201 | + /* Palavras-chave (e.g., public, final, class) */ |
| 202 | + .codehilite .kd {{ color: #f92672; font-weight: bold; }} /* Palavras-chave em rosa */ |
| 203 | +
|
| 204 | + /* Nome de classes (e.g., SaberToothedCat) */ |
| 205 | + .codehilite .nc {{ color: #a6e22e; font-weight: bold; }} /* Nome de classes em verde claro */ |
| 206 | +
|
| 207 | + /* Nomes de variáveis ou tipos (e.g., Animal, System) */ |
| 208 | + .codehilite .n {{ color: #66d9ef; }} /* Nomes de variáveis em azul claro */ |
| 209 | +
|
| 210 | + /* Anotações (e.g., @Override) */ |
| 211 | + .codehilite .nd {{ color: #ae81ff; font-style: italic; }} /* Anotações em roxo claro */ |
| 212 | +
|
| 213 | + /* Tipos de retorno (e.g., void) */ |
| 214 | + .codehilite .kt {{ color: #fd971f; }} /* Tipos de retorno em laranja */ |
| 215 | +
|
| 216 | + /* Nomes de métodos ou funções (e.g., makeSound) */ |
| 217 | + .codehilite .nf {{ color: #a6e22e; font-weight: bold; }} /* Nomes de métodos em verde claro */ |
| 218 | +
|
| 219 | + /* Comentários */ |
| 220 | + .codehilite .c1 {{ color: #75715e; font-style: italic; }} /* Comentários em cinza */ |
| 221 | +
|
| 222 | + /* Atributos ou membros (e.g., out, println) */ |
| 223 | + .codehilite .na {{ color: #f8f8f2; }} /* Atributos em branco */ |
| 224 | +
|
| 225 | + .codehilite .p {{ color: #f8f8f2; }} /* Pontuação em branco */ |
| 226 | +
|
| 227 | + /* Strings */ |
| 228 | + .codehilite .s {{ color: #e6db74; }} /* Strings em amarelo */ |
| 229 | +
|
| 230 | + /* Espaçamento (não precisa de estilização, mas está incluído para clareza) */ |
| 231 | + .codehilite .w {{ color: inherit; }} |
| 232 | + table {{ |
| 233 | + border-collapse: collapse; |
| 234 | + border-spacing: 0; |
| 235 | + max-width: 100%; |
| 236 | + display: block; |
| 237 | + overflow: auto; |
| 238 | + }} |
| 239 | + table th, table td {{ |
| 240 | + border: 1px solid #dfe2e5; |
| 241 | + padding: 6px 13px; |
| 242 | + }} |
| 243 | + table tr {{ |
| 244 | + background-color: #161b22; |
| 245 | + border-top: 1px solid #c6cbd1; |
| 246 | + }} |
| 247 | + table tr:nth-child(2n) {{ |
| 248 | + background-color: #30363d; |
| 249 | + }} |
| 250 | + input[type="checkbox"] {{ |
| 251 | + width: 1em; |
| 252 | + height: 1em; |
| 253 | + margin-right: 0.5em; |
| 254 | + vertical-align: middle; |
| 255 | + position: relative; |
| 256 | + top: -0.1em; |
| 257 | + }} |
| 258 | +
|
| 259 | + </style> |
| 260 | + </head> |
| 261 | + <body> |
| 262 | + {self.html_text_} |
| 263 | + </body> |
| 264 | + </html> |
| 265 | + """ |
| 266 | + |
| 267 | + def verifyChangesAndSetTabName(self): |
| 268 | + current_index = self.ui.tab_widget.currentIndex() |
| 269 | + current_tab = self.ui.tab_widget.widget(current_index) |
| 270 | + if current_tab in self.ui.open_files: |
| 271 | + # Atualiza o estado de isModified para True |
| 272 | + file_path = self.ui.open_files[current_tab][0] |
| 273 | + isModified = self.ui.open_files[current_tab][1] |
| 274 | + if not file_path.endswith('*') and not isModified: |
| 275 | + file_name = file_path.split('/')[-1] # Pega o último componente do caminho |
| 276 | + self.ui.tab_widget.setTabText(current_index, f"{file_name}*") |
| 277 | + self.ui.open_files[current_tab][1] = True |
0 commit comments