From b0a914c54d4904cca0d04e9fbd297a4937ccbe28 Mon Sep 17 00:00:00 2001 From: Abdullah Mustapha Date: Mon, 25 Dec 2023 13:10:57 +0000 Subject: [PATCH] Bug Fixes # Time/Date Insertion --- LICENSE | 30 +++++++----- main.py | 149 ++++++++++++++++++++++++++++++++++++++++++++------------ 2 files changed, 135 insertions(+), 44 deletions(-) diff --git a/LICENSE b/LICENSE index 6759f76..87dade2 100644 --- a/LICENSE +++ b/LICENSE @@ -1,17 +1,21 @@ -GNU GENERAL PUBLIC LICENSE -Version 3, 29 June 2007 +MIT License -Copyright (C) 2023 Abdullah O. Mustapha +Copyright (c) 2023 Abdullah Mustapha -This program is free software: you can redistribute it and/or modify -it under the terms of the GNU General Public License as published by -the Free Software Foundation, either version 3 of the License, or -(at your option) any later version. +Permission is hereby granted, free of charge, to any person obtaining a copy +of this software and associated documentation files (the "Software"), to deal +in the Software without restriction, including without limitation the rights +to use, copy, modify, merge, publish, distribute, sublicense, and/or sell +copies of the Software, and to permit persons to whom the Software is +furnished to do so, subject to the following conditions: -This program is distributed in the hope that it will be useful, -but WITHOUT ANY WARRANTY; without even the implied warranty of -MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the -GNU General Public License for more details. +The above copyright notice and this permission notice shall be included in +all copies or substantial portions of the Software. -You should have received a copy of the GNU General Public License -along with this program. If not, see . +THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR +IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, +FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE +AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER +LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, +OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN +THE SOFTWARE. diff --git a/main.py b/main.py index 1a96921..5d8e3e6 100644 --- a/main.py +++ b/main.py @@ -1,7 +1,10 @@ import sys -from PyQt5.QtWidgets import QApplication, QMainWindow, QAction, QFileDialog, QFontDialog, QVBoxLayout, QWidget, QMenuBar, QMessageBox, QPlainTextEdit -from PyQt5.QtCore import QDateTime, QUrl +from PyQt5.QtWidgets import QApplication, QMainWindow, QAction, QFileDialog, QFontDialog, QMessageBox, QPlainTextEdit, QStatusBar, QInputDialog +from PyQt5.QtCore import QDateTime, QUrl, QTextCodec from PyQt5.QtGui import QIcon, QFont, QDesktopServices +from PyQt5.QtPrintSupport import QPrintDialog, QPrinter + + class PlainTextEdit(QPlainTextEdit): @@ -17,7 +20,6 @@ def __init__(self, parent=None): self.setFont(default_font) def insertFromMimeData(self, mime_data): - # Override insertFromMimeData to prevent the automatic formatting of pasted text cursor = self.textCursor() cursor.insertText(mime_data.text()) self.setTextCursor(cursor) @@ -30,11 +32,19 @@ def __init__(self): # Initialize UI self.init_ui() + self.new_notepad = None + def init_ui(self): # Create a QTextEdit widget self.text_edit = PlainTextEdit(self) self.setCentralWidget(self.text_edit) + # Increase font size for the menu bar + menubar = self.menuBar() + menubar_font = menubar.font() + menubar_font.setPointSize(11) # Adjust the font size as needed + menubar.setFont(menubar_font) + # Create Find dialog self.setWindowIcon(QIcon('icon.ico')) # Create a menu bar @@ -44,22 +54,27 @@ def init_ui(self): file_menu = menubar.addMenu('File') new_action = QAction('New', self) + new_action.setShortcut("Ctrl+N") new_action.triggered.connect(self.new_file) file_menu.addAction(new_action) new_window_action = QAction('New Window', self) + new_window_action.setShortcut("Ctrl+Shift+N") new_window_action.triggered.connect(self.new_window) file_menu.addAction(new_window_action) open_action = QAction('Open', self) + open_action.setShortcut("Ctrl+O") open_action.triggered.connect(self.open_file) file_menu.addAction(open_action) save_action = QAction('Save', self) + save_action.setShortcut("Ctrl+S") save_action.triggered.connect(self.save_file) file_menu.addAction(save_action) save_as_action = QAction('Save As...', self) + save_as_action.setShortcut("Ctrl+Shift+S") save_as_action.triggered.connect(self.save_file_as) file_menu.addAction(save_as_action) @@ -70,12 +85,14 @@ def init_ui(self): file_menu.addAction(page_setup_action) print_action = QAction('Print', self) + print_action.setShortcut("Ctrl+P") print_action.triggered.connect(self.print_file) file_menu.addAction(print_action) file_menu.addSeparator() exit_action = QAction('Exit', self) + exit_action.setShortcut("Alt+F4") exit_action.triggered.connect(self.close) file_menu.addAction(exit_action) @@ -83,43 +100,39 @@ def init_ui(self): edit_menu = menubar.addMenu('Edit') undo_action = QAction('Undo', self) + undo_action.setShortcut("Ctrl+Z") undo_action.triggered.connect(self.text_edit.undo) edit_menu.addAction(undo_action) redo_action = QAction('Redo', self) + redo_action.setShortcut("Ctrl+Y") redo_action.triggered.connect(self.text_edit.redo) edit_menu.addAction(redo_action) edit_menu.addSeparator() cut_action = QAction('Cut', self) + cut_action.setShortcut("Ctrl+X") cut_action.triggered.connect(self.text_edit.cut) edit_menu.addAction(cut_action) copy_action = QAction('Copy', self) + copy_action.setShortcut("Ctrl+C") copy_action.triggered.connect(self.text_edit.copy) edit_menu.addAction(copy_action) paste_action = QAction('Paste', self) + paste_action.setShortcut("Ctrl+V") paste_action.triggered.connect(self.text_edit.paste) edit_menu.addAction(paste_action) delete_action = QAction('Delete', self) + delete_action.setShortcut("Del") delete_action.triggered.connect(self.text_edit.cut) # Delete is equivalent to Cut edit_menu.addAction(delete_action) edit_menu.addSeparator() - find_action = QAction('Find', self) - find_action.triggered.connect(self.find_dialog) - edit_menu.addAction(find_action) - - replace_action = QAction('Replace', self) - replace_action.triggered.connect(self.replace) - edit_menu.addAction(replace_action) - - edit_menu.addSeparator() - goto_action = QAction('Go To', self) goto_action.triggered.connect(self.goto_dialog) edit_menu.addAction(goto_action) @@ -127,10 +140,12 @@ def init_ui(self): edit_menu.addSeparator() select_all_action = QAction('Select All', self) + select_all_action.setShortcut("Ctrl+A") select_all_action.triggered.connect(self.text_edit.selectAll) edit_menu.addAction(select_all_action) time_date_action = QAction('Time/Date', self) + time_date_action.setShortcut("Ctrl+T") time_date_action.triggered.connect(self.insert_time_date) edit_menu.addAction(time_date_action) @@ -141,9 +156,37 @@ def init_ui(self): font_action.triggered.connect(self.change_font) format_menu.addAction(font_action) + wordWrap_action = QAction('Word wrap', self) + wordWrap_action.setCheckable(True) + format_menu.addAction(wordWrap_action) + # Create View menu view_menu = menubar.addMenu('View') + zoom_menu = view_menu.addMenu("Zoom...") + zoom_in = zoom_menu.addAction('Zoom in') + zoom_in.setShortcut("Ctrl+1") # Adjust the shortcut as needed + zoom_in.triggered.connect(self.zoom_in) + + zoom_out = zoom_menu.addAction('Zoom out') + zoom_out.setShortcut("Ctrl+0") # Adjust the shortcut as needed + zoom_out.triggered.connect(self.zoom_out) + + + # Create a status bar + self.status_bar = QStatusBar(self) + self.setStatusBar(self.status_bar) + + # Create a QAction for the status bar + status_bar_action = QAction('Status bar', self) + status_bar_action.setCheckable(True) + status_bar_action.setChecked(True) + status_bar_action.triggered.connect(self.toggle_status_bar) + view_menu.addAction(status_bar_action) + + self.update_status_bar + self.text_edit.textChanged.connect(self.update_status_bar) + # Create Help menu help_menu = menubar.addMenu('Help') @@ -164,32 +207,67 @@ def init_ui(self): # Set window properties self.setGeometry(100, 100, 800, 600) self.setWindowTitle('Notepad') - + + def toggle_status_bar(self, checked): + if checked: + self.status_bar.show() + else: + self.status_bar.hide() + def update_status_bar(self): + cursor = self.text_edit.textCursor() + block = cursor.block() + line_number = block.blockNumber() + 1 + column_number = cursor.positionInBlock() + 1 + + # Get plain text content + plain_text = self.text_edit.toPlainText() + + # Use QTextCodec to detect the encoding + codec = QTextCodec.codecForName("UTF-8") + encoding = codec.name() + + status_text = f'Ln {line_number}, Col {column_number} | Encoding: {encoding}' + self.status_bar.showMessage(status_text) + + def zoom_in(self): + self.text_edit.zoomIn(1) + def zoom_out(self): + self.text_edit.zoomOut(1) def view_help(self): - feedback_url = 'https://github.com/abdullahCoder-Tech/Notepad' + feedback_url = 'https://github.com/abdullahCoder-Tech/Notepad/blob/main/README.md' QDesktopServices.openUrl(QUrl(feedback_url)) def send_feedback(self): # Open the feedback URL in the default web browser - feedback_url = 'https://github.com/abdullahCoder-Tech/Notepad' + feedback_url = 'https://github.com/abdullahCoder-Tech/Notepad/issues/new' QDesktopServices.openUrl(QUrl(feedback_url)) def about_notepad(self): about_message = ( - "Notepad Application\n" + "About this app\n\n" "Author: Abdullah O. Mustapha\n" - "Version: v1.0\n" - "License: GNU General Public License\n" - "\n\nAll Rights Reserved" + "Version: v1.0.1\n" + "License: MIT License\n" + "\n\n© 2023 Drop Dev. All rights reserved" ) QMessageBox.information(self, 'About Notepad', about_message, QMessageBox.Ok) def new_file(self): self.text_edit.clear() + # def new_window(self): + # new_notepad = Notepad() + # new_notepad.show() + def new_window(self): - new_notepad = Notepad() - new_notepad.show() + # Check if a new window is already open + if self.new_notepad is None or not self.new_notepad.isVisible(): + # Create a new instance of Notepad + self.new_notepad = Notepad() + self.new_notepad.show() + else: + # Bring the existing window to front + self.new_notepad.raise_() def open_file(self): options = QFileDialog.Options() @@ -208,31 +286,40 @@ def save_file(self): def save_file_as(self): options = QFileDialog.Options() - file_name, _ = QFileDialog.getSaveFileName(self, 'Save File', '', 'Text Files (*.txt);;All Files (.)', options=options) + file_name, _ = QFileDialog.getSaveFileName(self, 'Save File', '', 'Text Files (*.txt);;C++ Files (*.cpp);;C Files(*.c);;Python Files (*.py);;All Files (.)', options=options) if file_name: self.current_file = file_name self.save_file() def page_setup(self): - QMessageBox.information(self, 'Info', 'Coming Soon', QMessageBox.Ok) + pass + def print_file(self): - QMessageBox.information(self, 'Info', 'Coming Soon', QMessageBox.Ok) + printer = QPrinter(QPrinter.HighResolution) + dialog = QPrintDialog(printer, self) + if dialog.exec_() == QPrintDialog.Accepted: + self.text_edit.print_(printer) def change_font(self): font, ok = QFontDialog.getFont(self.text_edit.font(), self) if ok: self.text_edit.setFont(font) - def goto_dialog(self): - QMessageBox.information(self, 'Info', 'Coming Soon', QMessageBox.Ok) + # Get the total number of lines in the text editor + total_lines = self.text_edit.document().blockCount() - def replace(self): - QMessageBox.information(self, 'Info', 'Coming Soon', QMessageBox.Ok) + # Display an input dialog for the user to enter the line number + line_number, ok = QInputDialog.getInt(self, 'Go To Line', 'Enter Line Number (1 - {}):'.format(total_lines), min=1, max=total_lines) - def find_dialog(self): - QMessageBox.information(self, 'Info', 'Coming Soon', QMessageBox.Ok) + if ok: + # Move the cursor to the specified line + cursor = self.text_edit.textCursor() + cursor.movePosition(cursor.Start) + for _ in range(line_number - 1): + cursor.movePosition(cursor.NextBlock) + self.text_edit.setTextCursor(cursor) def insert_time_date(self): current_date_time = QDateTime.currentDateTime().toString('MM/dd/yyyy hh:mm:ss AP')