-
-
+
- About eSim
-
-
-
- eSim is an open source EDA tool for circuit design, simulation, analysis and PCB design. It is an integrated tool built using open source softwares such as KiCad (https://www.kicad.org/), Ngspice (https://ngspice.sourceforge.io/), GHDL (http://ghdl.free.fr), Verilator (https://www.veripool.org/verilator/), Makerchip IDE (https://www.makerchip.com/), and SkyWater SKY130 PDK (https://skywater-pdk.rtfd.io/). eSim source is released under GNU General Public License.
-
-
-
- This tool is developed by the eSim Team at FOSSEE, IIT Bombay. To know more about eSim, please visit: https://esim.fossee.in/.
-
-
-
- To discuss more about eSim, please visit: https://forums.fossee.in/
-
-
+
+
About eSim
+

+
+ eSim is an open source EDA tool for circuit design, simulation, analysis and PCB design. It is an integrated tool built using open source softwares such as
+ KiCad,
+ Ngspice,
+ GHDL,
+ Verilator,
+ Makerchip IDE, and
+ SkyWater SKY130 PDK.
+ eSim source is released under GNU General Public License.
+
+
+ This tool is developed by the eSim Team at FOSSEE, IIT Bombay.
+ To know more about eSim, please visit:
+ https://esim.fossee.in/.
+
+
+ To discuss more about eSim, please visit:
+ https://forums.fossee.in/
+
+
-
-
+
\ No newline at end of file
diff --git a/src/browser/HTMLUserManual.py b/src/browser/HTMLUserManual.py
new file mode 100644
index 000000000..aa773f945
--- /dev/null
+++ b/src/browser/HTMLUserManual.py
@@ -0,0 +1,602 @@
+from PyQt5 import QtWidgets, QtCore
+from PyQt5.QtGui import QPalette, QColor
+import os
+import re
+
+class HTMLUserManual(QtWidgets.QWidget):
+ """
+ This class displays the user manual in a widget with proper theme switching.
+ """
+
+ def __init__(self, is_dark_theme=False):
+ super().__init__()
+ self.is_dark_theme = is_dark_theme
+ self.original_html_content = None
+ self.vlayout = QtWidgets.QVBoxLayout()
+ self.browser = QtWidgets.QTextBrowser()
+ self.vlayout.addWidget(self.browser)
+ self.setLayout(self.vlayout)
+
+ # Set margins for a more professional look
+ self.vlayout.setContentsMargins(0, 0, 0, 0)
+
+ self.load_original_html()
+ self.set_manual_html()
+ self.show()
+
+ def load_original_html(self):
+ """Load the original HTML content once and store it."""
+ path_from_script = os.path.join(os.path.dirname(__file__), '..', '..', 'library', 'browser', 'User-Manual', 'eSim.html')
+ try:
+ with open(path_from_script, 'r', encoding='utf-8') as f:
+ self.original_html_content = f.read()
+ # Set search paths for images
+ self.browser.setSearchPaths([os.path.dirname(path_from_script)])
+ except FileNotFoundError:
+ self.original_html_content = f"Error: User manual file not found
Path: {os.path.realpath(path_from_script)}
"
+
+ def get_base_styles(self):
+ """Get the base styles that work for both themes."""
+ return '''
+
+ '''
+
+ def get_light_theme_styles(self):
+ """Get styles specific to light theme."""
+ return '''
+
+ '''
+
+ def get_dark_theme_styles(self):
+ """Get styles specific to dark theme."""
+ return '''
+
+ '''
+
+ def clean_existing_styles(self, html_content):
+ """Remove any existing injected styles more thoroughly."""
+ # Remove styles with specific IDs
+ html_content = re.sub(r'', '', html_content, flags=re.DOTALL | re.IGNORECASE)
+
+ # Remove old style comments
+ html_content = re.sub(r'
+
+
+ Terminal Simulation Console
+
+
+ Qt::AlignCenter
+
+
+
-
-
+
+
- 6
+ 16
0
-
-
-
-
- 0
- 0
-
+
+
+
+ 8
-
-
- 16777215
- 35
-
-
-
- QProgressBar::chunk {
- background-color: rgb(54,158,225);
-}
-
-
- 0
-
-
- -1
-
-
-
-
-
+
-
+
+
+ Simulation Progress
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 300
+ 40
+
+
+
+
+ 16777215
+ 40
+
+
+
+ 0
+
+
+ -1
+
+
+
+
+
+
+
-
-
-
+
+
+
+ Qt::Horizontal
+
+
+ QSizePolicy::MinimumExpanding
+
+
- 16777215
- 35
+ 20
+ 20
-
- Resimulate
-
-
+
-
-
-
-
- 16777215
- 35
-
+
+
+
+ 12
+
-
+
+
+
+ 140
+ 56
+
+
+
+
+ 16777215
+ 56
+
+
+
+ ↻ Resimulate
+
+
+ Start a new simulation
+
+
+
+ -
+
+
+
+ 140
+ 56
+
+
+
+
+ 16777215
+ 56
+
+
+
+ ✕ Cancel
+
+
+ Stop current simulation
+
+
+
+ -
+
+
+
+ 0
+ 0
+
+
+
+
+ 40
+ 40
+
+
+
+
+ 40
+ 40
+
+
+
+ ☀
+
+
+ Toggle light/dark mode
+
+
+
+
+
+
+
+ -
+
+
+
+ 12
+
+
-
+
- Cancel Simulation
+ Console Output
-
-
+
-
+
0
- 0
+ 1
-
+
- 35
- 35
+ 0
+ 350
-
-
+
+ Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
+
+
+ Simulation output will appear here...
- -
-
-
-
- 0
- 0
-
-
-
-
- 0
- 400
-
-
-
- QTextEdit {
- background-color: rgb(36, 31, 49);
- color: white;
-}
-
-
- <!DOCTYPE HTML PUBLIC "-//W3C//DTD HTML 4.0//EN" "http://www.w3.org/TR/REC-html40/strict.dtd">
-<html><head><meta name="qrichtext" content="1" /><style type="text/css">
-p, li { white-space: pre-wrap; }
-</style></head><body style=" font-family:'Ubuntu'; font-size:11pt; font-weight:400; font-style:normal;">
-<p style="-qt-paragraph-type:empty; margin-top:0px; margin-bottom:0px; margin-left:0px; margin-right:0px; -qt-block-indent:0; text-indent:0px;"><br /></p></body></html>
-
-
- Qt::LinksAccessibleByKeyboard|Qt::LinksAccessibleByMouse|Qt::TextBrowserInteraction|Qt::TextSelectableByKeyboard|Qt::TextSelectableByMouse
-
-
-
-
+
\ No newline at end of file
diff --git a/src/frontEnd/color_science.py b/src/frontEnd/color_science.py
new file mode 100644
index 000000000..993266e47
--- /dev/null
+++ b/src/frontEnd/color_science.py
@@ -0,0 +1,126 @@
+"""
+Modern Color Science Module for eSim
+References:
+- Elliot & Maier (2007): https://doi.org/10.1037/0003-066X.62.4.313
+- Singh (2006): https://doi.org/10.1080/0013188042000277323
+- Whitfield & Wiltshire (1990): https://doi.org/10.1016/0272-4944(90)90047-3
+- Robins & Holmes (2008): https://doi.org/10.1007/s10799-007-0037-4
+- Bottomley & Doyle (2006): https://doi.org/10.1002/bdm.515
+- W3C WCAG 2.1: https://www.w3.org/TR/WCAG21/
+- Rigden (1999): https://doi.org/10.1109/38.768554
+"""
+import colorsys
+from typing import Tuple, List, Dict
+
+class ColorScience:
+ # Color psychology mapping (simplified)
+ PSYCHOLOGY = {
+ 'blue': {'emotion': 'trust', 'hex': '#2563EB'},
+ 'green': {'emotion': 'success', 'hex': '#059669'},
+ 'orange': {'emotion': 'energy', 'hex': '#D97706'},
+ 'red': {'emotion': 'error', 'hex': '#DC2626'},
+ 'purple': {'emotion': 'creativity', 'hex': '#7C3AED'},
+ 'gray': {'emotion': 'neutral', 'hex': '#64748B'},
+ }
+ # WCAG 2.1 contrast ratios
+ WCAG_AA = 4.5
+ WCAG_AAA = 7.0
+ # Colorblind simulation matrices (protanopia, deuteranopia, tritanopia)
+ COLORBLIND_MATRICES = {
+ 'protanopia': (0.56667, 0.43333, 0, 0.55833, 0.44167, 0, 0, 0.24167, 0.75833),
+ 'deuteranopia':(0.625, 0.375, 0, 0.7, 0.3, 0, 0, 0.3, 0.7),
+ 'tritanopia': (0.95, 0.05, 0, 0, 0.43333, 0.56667, 0, 0.475, 0.525),
+ }
+ # Semantic color system
+ SEMANTIC = {
+ 'primary': '#2563EB',
+ 'secondary': '#7C3AED',
+ 'success': '#059669',
+ 'warning': '#D97706',
+ 'error': '#DC2626',
+ 'info': '#0891B2',
+ 'background_light': '#FFFFFF',
+ 'background_dark': '#181b24',
+ 'text_light': '#2c3e50',
+ 'text_dark': '#e8eaed',
+ }
+
+ @staticmethod
+ def hex_to_rgb(hex_color: str) -> Tuple[float, float, float]:
+ hex_color = hex_color.lstrip('#')
+ return tuple(int(hex_color[i:i+2], 16)/255 for i in (0, 2, 4))
+
+ @staticmethod
+ def rgb_to_hex(r: float, g: float, b: float) -> str:
+ return f"#{int(r*255):02x}{int(g*255):02x}{int(b*255):02x}"
+
+ @staticmethod
+ def contrast_ratio(hex1: str, hex2: str) -> float:
+ def luminance(rgb):
+ r, g, b = [x/12.92 if x <= 0.03928 else ((x+0.055)/1.055)**2.4 for x in rgb]
+ return 0.2126*r + 0.7152*g + 0.0722*b
+ l1 = luminance(ColorScience.hex_to_rgb(hex1))
+ l2 = luminance(ColorScience.hex_to_rgb(hex2))
+ lighter, darker = max(l1, l2), min(l1, l2)
+ return (lighter+0.05)/(darker+0.05)
+
+ @staticmethod
+ def ensure_wcag(fg: str, bg: str, level: str = 'AA') -> bool:
+ ratio = ColorScience.contrast_ratio(fg, bg)
+ return ratio >= (ColorScience.WCAG_AAA if level == 'AAA' else ColorScience.WCAG_AA)
+
+ @staticmethod
+ def simulate_colorblind(hex_color: str, mode: str = 'deuteranopia') -> str:
+ r, g, b = ColorScience.hex_to_rgb(hex_color)
+ m = ColorScience.COLORBLIND_MATRICES.get(mode)
+ if not m:
+ return hex_color
+ r2 = r*m[0] + g*m[1] + b*m[2]
+ g2 = r*m[3] + g*m[4] + b*m[5]
+ b2 = r*m[6] + g*m[7] + b*m[8]
+ return ColorScience.rgb_to_hex(r2, g2, b2)
+
+ @staticmethod
+ def harmonious_palette(base: str, mode: str = 'analogous', n: int = 5) -> List[str]:
+ # Generate harmonious palette using HSL
+ r, g, b = ColorScience.hex_to_rgb(base)
+ h, l, s = colorsys.rgb_to_hls(r, g, b)
+ palette = []
+ if mode == 'analogous':
+ for i in range(n):
+ h2 = (h + (i - n//2)*0.08) % 1.0
+ rgb = colorsys.hls_to_rgb(h2, l, s)
+ palette.append(ColorScience.rgb_to_hex(*rgb))
+ elif mode == 'complementary':
+ palette = [base, ColorScience.rgb_to_hex(*colorsys.hls_to_rgb((h+0.5)%1.0, l, s))]
+ elif mode == 'triadic':
+ palette = [ColorScience.rgb_to_hex(*colorsys.hls_to_rgb((h+shift)%1.0, l, s)) for shift in (0, 1/3, 2/3)]
+ else:
+ palette = [base]
+ return palette
+
+ @staticmethod
+ def adaptive_theme(is_dark: bool) -> Dict[str, str]:
+ # Adaptive color scheme
+ if is_dark:
+ return {
+ 'background': ColorScience.SEMANTIC['background_dark'],
+ 'text': ColorScience.SEMANTIC['text_dark'],
+ 'primary': ColorScience.SEMANTIC['primary'],
+ 'secondary': ColorScience.SEMANTIC['secondary'],
+ 'success': ColorScience.SEMANTIC['success'],
+ 'warning': ColorScience.SEMANTIC['warning'],
+ 'error': ColorScience.SEMANTIC['error'],
+ 'info': ColorScience.SEMANTIC['info'],
+ }
+ else:
+ return {
+ 'background': ColorScience.SEMANTIC['background_light'],
+ 'text': ColorScience.SEMANTIC['text_light'],
+ 'primary': ColorScience.SEMANTIC['primary'],
+ 'secondary': ColorScience.SEMANTIC['secondary'],
+ 'success': ColorScience.SEMANTIC['success'],
+ 'warning': ColorScience.SEMANTIC['warning'],
+ 'error': ColorScience.SEMANTIC['error'],
+ 'info': ColorScience.SEMANTIC['info'],
+ }
\ No newline at end of file
diff --git a/src/frontEnd/sim_setup_fix.py b/src/frontEnd/sim_setup_fix.py
new file mode 100644
index 000000000..30da5be7f
--- /dev/null
+++ b/src/frontEnd/sim_setup_fix.py
@@ -0,0 +1,168 @@
+#!/usr/bin/env python3
+"""
+eSim Setup and Configuration Fix
+This script fixes common setup issues with eSim application including:
+1. Creating missing .esim directory
+2. Setting up workspace configuration
+3. Creating default workspace directory
+4. Fixing file permissions
+"""
+
+import os
+import sys
+from pathlib import Path
+
+def create_esim_config():
+ """Create the .esim configuration directory and files"""
+ try:
+ # Get user home directory
+ if os.name == 'nt':
+ # Windows path handling (as per the original code)
+ user_home = os.path.join('library', 'config')
+ else:
+ # Unix/Linux path handling
+ user_home = os.path.expanduser('~')
+
+ esim_config_dir = os.path.join(user_home, '.esim')
+
+ # Create .esim directory if it doesn't exist
+ if not os.path.exists(esim_config_dir):
+ os.makedirs(esim_config_dir, exist_ok=True)
+ print(f"✓ Created .esim configuration directory: {esim_config_dir}")
+ else:
+ print(f"✓ .esim directory already exists: {esim_config_dir}")
+
+ # Create workspace.txt file if it doesn't exist
+ workspace_file = os.path.join(esim_config_dir, 'workspace.txt')
+ if not os.path.exists(workspace_file):
+ with open(workspace_file, 'w') as f:
+ f.write('1') # Default value indicating workspace is set
+ print(f"✓ Created workspace configuration: {workspace_file}")
+ else:
+ print(f"✓ Workspace configuration already exists: {workspace_file}")
+
+ # Create default eSim workspace directory
+ default_workspace = os.path.join(user_home, 'eSim-Workspace')
+ if not os.path.exists(default_workspace):
+ os.makedirs(default_workspace, exist_ok=True)
+ print(f"✓ Created default workspace directory: {default_workspace}")
+ else:
+ print(f"✓ Default workspace directory already exists: {default_workspace}")
+
+ # Set proper permissions (Unix/Linux only)
+ if os.name != 'nt':
+ os.chmod(esim_config_dir, 0o755)
+ os.chmod(workspace_file, 0o644)
+ if os.path.exists(default_workspace):
+ os.chmod(default_workspace, 0o755)
+ print("✓ Set proper file permissions")
+
+ return True
+
+ except Exception as e:
+ print(f"✗ Error creating eSim configuration: {e}")
+ return False
+
+def check_dependencies():
+ """Check if required Python packages are available"""
+ missing_packages = []
+
+ try:
+ import PyQt5
+ print("✓ PyQt5 is available")
+ except ImportError:
+ missing_packages.append("PyQt5")
+ print("✗ PyQt5 is missing")
+
+ try:
+ from PyQt5 import QtGui, QtCore, QtWidgets
+ print("✓ PyQt5 components are available")
+ except ImportError:
+ print("✗ PyQt5 components are missing or incomplete")
+
+ if missing_packages:
+ print(f"\n⚠️ Missing packages: {', '.join(missing_packages)}")
+ print("Install them using: pip install " + " ".join(missing_packages))
+ return False
+
+ return True
+
+def fix_application_py():
+ """Provide suggested fix for the Application.py file"""
+ fix_code = '''
+# Add this function to your Workspace.py file in the createWorkspace method
+# Before trying to open the workspace.txt file for writing:
+
+def ensure_config_directory():
+ """Ensure .esim configuration directory exists"""
+ if os.name == 'nt':
+ user_home = os.path.join('library', 'config')
+ else:
+ user_home = os.path.expanduser('~')
+
+ esim_config_dir = os.path.join(user_home, '.esim')
+
+ # Create directory if it doesn't exist
+ if not os.path.exists(esim_config_dir):
+ os.makedirs(esim_config_dir, exist_ok=True)
+
+ return user_home
+
+# Then modify your createWorkspace method to call this function first:
+# user_home = ensure_config_directory()
+# file = open(os.path.join(user_home, ".esim/workspace.txt"), 'w')
+'''
+
+ print("\n" + "="*60)
+ print("SUGGESTED CODE FIX FOR WORKSPACE.PY:")
+ print("="*60)
+ print(fix_code)
+
+def main():
+ """Main function to run all fixes"""
+ print("eSim Configuration Fix Tool")
+ print("="*30)
+
+ # Check Python version
+ if sys.version_info < (3, 6):
+ print("⚠️ Warning: Python 3.6+ recommended for eSim")
+
+ # Check dependencies
+ print("\n1. Checking dependencies...")
+ deps_ok = check_dependencies()
+
+ # Create configuration
+ print("\n2. Setting up eSim configuration...")
+ config_ok = create_esim_config()
+
+ # Provide code fix suggestions
+ print("\n3. Code fix suggestions...")
+ fix_application_py()
+
+ # Summary
+ print("\n" + "="*60)
+ print("SETUP SUMMARY:")
+ print("="*60)
+
+ if config_ok:
+ print("✓ Configuration setup completed successfully")
+ print("✓ You can now try running eSim again")
+
+ if not deps_ok:
+ print("⚠️ Some dependencies are missing - install them first")
+
+ print("\nNext steps:")
+ print("1. Apply the suggested code fix to Workspace.py")
+ print("2. Run: python Application.py")
+
+ else:
+ print("✗ Configuration setup failed")
+ print("Please check file permissions and try again")
+
+ print("\nIf you still encounter issues:")
+ print("- Check file permissions in your home directory")
+ print("- Make sure you have write access to ~/.esim/")
+ print("- Verify all Python dependencies are installed")
+
+if __name__ == "__main__":
+ main()
diff --git a/src/frontEnd/simple_app.py b/src/frontEnd/simple_app.py
new file mode 100644
index 000000000..22cd2828b
--- /dev/null
+++ b/src/frontEnd/simple_app.py
@@ -0,0 +1,33 @@
+#!/usr/bin/env python3
+import sys
+from PyQt5 import QtWidgets
+
+def main():
+ app = QtWidgets.QApplication(sys.argv)
+ window = QtWidgets.QMainWindow()
+ window.setWindowTitle("eSim Simple App")
+ window.setGeometry(100, 100, 800, 600)
+
+ # Create a central widget
+ central_widget = QtWidgets.QWidget()
+ window.setCentralWidget(central_widget)
+
+ # Create a layout
+ layout = QtWidgets.QVBoxLayout()
+ central_widget.setLayout(layout)
+
+ # Add a label
+ label = QtWidgets.QLabel("eSim is running!")
+ label.setStyleSheet("font-size: 24px; color: #1976d2;")
+ layout.addWidget(label)
+
+ # Add a button
+ button = QtWidgets.QPushButton("Click Me")
+ button.clicked.connect(lambda: label.setText("Button clicked!"))
+ layout.addWidget(button)
+
+ window.show()
+ sys.exit(app.exec_())
+
+if __name__ == "__main__":
+ main()
\ No newline at end of file
diff --git a/src/frontEnd/tab_colors_config.py b/src/frontEnd/tab_colors_config.py
new file mode 100644
index 000000000..634201ef5
--- /dev/null
+++ b/src/frontEnd/tab_colors_config.py
@@ -0,0 +1,161 @@
+"""
+Tab Color Configuration for eSim
+This file allows easy customization of tab colors for both dark and light themes.
+"""
+
+# Dark Theme Tab Colors
+DARK_TAB_COLORS = {
+ # Normal tab state
+ 'background': {
+ 'start': '#2d3748', # Top gradient color
+ 'end': '#1a202c' # Bottom gradient color
+ },
+ 'text': '#e2e8f0', # Tab text color
+ 'border': '#4a5568', # Tab border color
+
+ # Selected tab state
+ 'selected_background': '#667eea', # Selected tab background (purple)
+ 'selected_text': '#1a202c', # Selected tab text
+ 'selected_border': '#667eea', # Selected tab border
+
+ # Hover state
+ 'hover_background': '#4a5568', # Hover background
+ 'hover_text': '#f7fafc' # Hover text color
+}
+
+# Light Theme Tab Colors
+LIGHT_TAB_COLORS = {
+ # Normal tab state
+ 'background': {
+ 'start': '#ffffff', # Top gradient color
+ 'end': '#f8f9fa' # Bottom gradient color
+ },
+ 'text': '#2c3e50', # Tab text color
+ 'border': '#e1e4e8', # Tab border color
+
+ # Selected tab state
+ 'selected_background': '#1976d2', # Selected tab background (blue)
+ 'selected_text': '#ffffff', # Selected tab text
+ 'selected_border': '#1976d2', # Selected tab border
+
+ # Hover state
+ 'hover_background': '#f1f4f9', # Hover background
+ 'hover_text': '#1976d2' # Hover text color
+}
+
+# Alternative Color Schemes (you can uncomment and use these)
+
+# Purple Theme
+PURPLE_TAB_COLORS = {
+ 'background': {'start': '#553c9a', 'end': '#b794f4'},
+ 'text': '#e9d8fd',
+ 'border': '#9f7aea',
+ 'selected_background': '#d53f8c',
+ 'selected_text': '#fed7e2',
+ 'selected_border': '#d53f8c',
+ 'hover_background': '#9f7aea',
+ 'hover_text': '#faf5ff'
+}
+
+# Green Theme
+GREEN_TAB_COLORS = {
+ 'background': {'start': '#22543d', 'end': '#38a169'},
+ 'text': '#c6f6d5',
+ 'border': '#48bb78',
+ 'selected_background': '#38a169',
+ 'selected_text': '#f0fff4',
+ 'selected_border': '#38a169',
+ 'hover_background': '#48bb78',
+ 'hover_text': '#f0fff4'
+}
+
+# Orange Theme
+ORANGE_TAB_COLORS = {
+ 'background': {'start': '#744210', 'end': '#ed8936'},
+ 'text': '#fed7aa',
+ 'border': '#f6ad55',
+ 'selected_background': '#dd6b20',
+ 'selected_text': '#fffaf0',
+ 'selected_border': '#dd6b20',
+ 'hover_background': '#f6ad55',
+ 'hover_text': '#fffaf0'
+}
+
+def get_tab_stylesheet(colors, theme_type='dark'):
+ """
+ Generate CSS stylesheet for tabs based on color configuration.
+
+ Args:
+ colors (dict): Color configuration dictionary
+ theme_type (str): 'dark' or 'light' theme
+
+ Returns:
+ str: CSS stylesheet string
+ """
+ return f"""
+ QTabBar::tab {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {colors['background']['start']}, stop:1 {colors['background']['end']});
+ color: {colors['text']};
+ border: 1px solid {colors['border']};
+ border-bottom: none;
+ border-top-left-radius: 12px;
+ border-top-right-radius: 12px;
+ padding: 12px 28px;
+ margin-right: 4px;
+ font-weight: 600;
+ font-size: 13px;
+ letter-spacing: 0.3px;
+ }}
+ QTabBar::tab:selected {{
+ background: {colors['selected_background']};
+ color: {colors['selected_text']};
+ border: 1px solid {colors['selected_border']};
+ border-bottom: 3px solid {colors['selected_border']};
+ font-weight: 700;
+ }}
+ QTabBar::tab:hover:!selected {{
+ background: {colors['hover_background']};
+ color: {colors['hover_text']};
+ }}
+ QTabWidget::pane {{
+ border: 1px solid {colors['border']};
+ border-radius: 0 12px 12px 12px;
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {colors['background']['start']}, stop:1 {colors['background']['end']});
+ }}
+ """
+
+def apply_custom_tab_colors(application, dark_colors=None, light_colors=None):
+ """
+ Apply custom tab colors to the application.
+
+ Args:
+ application: The main application instance
+ dark_colors (dict): Custom dark theme colors (optional)
+ light_colors (dict): Custom light theme colors (optional)
+ """
+ if dark_colors is None:
+ dark_colors = DARK_TAB_COLORS
+ if light_colors is None:
+ light_colors = LIGHT_TAB_COLORS
+
+ # Store the custom colors in the application for later use
+ application.custom_dark_tab_colors = dark_colors
+ application.custom_light_tab_colors = light_colors
+
+ # Apply the current theme
+ if hasattr(application, 'is_dark_theme') and application.is_dark_theme:
+ application.apply_dark_theme()
+ else:
+ application.apply_light_theme()
+
+# Example usage:
+# To use purple theme for dark mode:
+# apply_custom_tab_colors(app, dark_colors=PURPLE_TAB_COLORS)
+
+# To use green theme for light mode:
+# apply_custom_tab_colors(app, light_colors=GREEN_TAB_COLORS)
+
+# To use both:
+# apply_custom_tab_colors(app, dark_colors=PURPLE_TAB_COLORS, light_colors=GREEN_TAB_COLORS)
\ No newline at end of file
diff --git a/src/kicadtoNgspice/KicadtoNgspice.py b/src/kicadtoNgspice/KicadtoNgspice.py
index e018143fe..4ebc4271f 100644
--- a/src/kicadtoNgspice/KicadtoNgspice.py
+++ b/src/kicadtoNgspice/KicadtoNgspice.py
@@ -230,7 +230,41 @@ def createcreateConvertWidget(self):
self.microcontrollerTab.setWidgetResizable(True)
self.tabWidget = QtWidgets.QTabWidget()
- # self.tabWidget.TabShape(QtWidgets.QTabWidget.Rounded)
+ # Apply custom stylesheet to ensure tab text is not cropped and matches Makerchip/NgVeri style
+ self.tabWidget.setStyleSheet('''
+ QTabWidget::pane {
+ border: 2px solid #23273a;
+ border-radius: 14px;
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 #23273a, stop:1 #181b24);
+ }
+ QTabBar::tab {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 #23273a, stop:1 #181b24);
+ color: #e8eaed;
+ border: 1px solid #40c4ff;
+ border-bottom: none;
+ border-top-left-radius: 8px;
+ border-top-right-radius: 8px;
+ padding: 12px 28px;
+ margin-right: 2px;
+ font-size: 14px;
+ font-weight: bold;
+ min-width: 150px;
+ max-width: 300px;
+ }
+ QTabBar::tab:selected {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 #40c4ff, stop:1 #1976d2);
+ color: #181b24;
+ border: 1px solid #40c4ff;
+ border-bottom: none;
+ }
+ QTabBar::tab:hover:!selected {
+ background: #1976d2;
+ color: #fff;
+ }
+ ''')
self.tabWidget.addTab(self.analysisTab, "Analysis")
self.tabWidget.addTab(self.sourceTab, "Source Details")
self.tabWidget.addTab(self.modelTab, "Ngspice Model")
diff --git a/src/maker/Appconfig.py b/src/maker/Appconfig.py
index 315ecff81..93289eed7 100755
--- a/src/maker/Appconfig.py
+++ b/src/maker/Appconfig.py
@@ -1,52 +1,198 @@
-import os.path
-from configparser import ConfigParser
+#!/usr/bin/env python3
+"""
+eSim Configuration File Fix
+This script fixes the configuration file format issue in Appconfig.py
+The error occurs when the workspace configuration file doesn't have the expected format.
+"""
+import os
+import sys
+from pathlib import Path
-class Appconfig:
+def diagnose_config_files():
+ """Diagnose the current state of eSim configuration files"""
+ print("Diagnosing eSim Configuration Files")
+ print("=" * 40)
+
+ # Determine user home directory
if os.name == 'nt':
- home = os.path.join('library', 'config')
+ user_home = os.path.join('library', 'config')
else:
- home = os.path.expanduser('~')
+ user_home = os.path.expanduser('~')
+
+ esim_config_dir = os.path.join(user_home, '.esim')
+ workspace_file = os.path.join(esim_config_dir, 'workspace.txt')
+
+ print(f"User home: {user_home}")
+ print(f"Config directory: {esim_config_dir}")
+ print(f"Workspace file: {workspace_file}")
+
+ # Check if directory exists
+ if not os.path.exists(esim_config_dir):
+ print("✗ .esim directory does not exist")
+ return False, esim_config_dir, workspace_file
+ else:
+ print("✓ .esim directory exists")
+
+ # Check if workspace.txt exists
+ if not os.path.exists(workspace_file):
+ print("✗ workspace.txt does not exist")
+ return False, esim_config_dir, workspace_file
+ else:
+ print("✓ workspace.txt exists")
+
+ # Read and analyze workspace.txt content
+ print("\nAnalyzing workspace.txt content:")
+ try:
+ with open(workspace_file, 'r') as f:
+ content = f.read()
+ lines = content.strip().split('\n')
+
+ print(f"File content: '{content}'")
+ print(f"Number of lines: {len(lines)}")
+
+ if lines:
+ first_line = lines[0].strip()
+ print(f"First line: '{first_line}'")
+
+ # Check if first line has space-separated values
+ parts = first_line.split(' ')
+ print(f"Parts when split by space: {parts} (count: {len(parts)})")
+
+ if len(parts) < 2:
+ print("✗ First line doesn't have expected format (workspace_check home_path)")
+ return False, esim_config_dir, workspace_file
+ else:
+ print("✓ First line has correct format")
+
+ except Exception as e:
+ print(f"✗ Error reading workspace.txt: {e}")
+ return False, esim_config_dir, workspace_file
+
+ return True, esim_config_dir, workspace_file
- # Reading all variables from eSim config.ini
- parser_esim = ConfigParser()
- parser_esim.read(os.path.join(home, os.path.join('.esim', 'config.ini')))
+def create_proper_workspace_config(esim_config_dir, workspace_file):
+ """Create a properly formatted workspace configuration"""
try:
- src_home = parser_esim.get('eSim', 'eSim_HOME')
- xml_loc = os.path.join(src_home, 'library/modelParamXML')
- lib_loc = os.path.expanduser('~')
- except BaseException:
- pass
- esimFlag = 0
-
- # Reading all variables from ngveri config.ini
- # parser_ngveri = ConfigParser()
- # parser_ngveri.read(os.path.join(home,
- # os.path.join('.ngveri', 'config.ini')))
-
- # KiCad v6 Library Template
- kicad_sym_template = {
- "start_def": "(symbol \"comp_name\" (pin_names (offset 1.016)) " +
- "(in_bom yes) (on_board yes)",
- "U_field": "(property \"Reference\" \"U\" (id 0) (at 12 15 0)" +
- "(effects (font (size 1.524 1.524))))",
- "comp_name_field": "(property \"Value\" \"comp_name\" (id 1) " +
- "(at 12 18 0)(effects (font (size 1.524 1.524))))",
- "blank_field": [
- "(property \"Footprint\" blank_quotes (id 2) " +
- "(at 72.39 49.53 0)(effects (font (size 1.524 1.524))))",
- "(property \"Datasheet\" blank_quotes (id 3) " +
- "(at 72.39 49.53 0)(effects (font (size 1.524 1.524))))"
- ],
- "draw_pos": "(symbol \"comp_name\"(rectangle (start 0 0 ) " +
- "(end 25.40 3.6 )(stroke (width 0) (type default) " +
- "(color 0 0 0 0))(fill (type none))))",
- "start_draw": "(symbol",
- "input_port": "(pin input line(at -5.15 0.54 0 )(length 5.08 )" +
- "(name \"in\" (effects(font(size 1.27 1.27))))" +
- "(number \"1\" (effects (font (size 1.27 1.27)))))",
- "output_port": "(pin output line(at 30.52 0.54 180 )(length 5.08 )" +
- "(name \"out\" (effects(font(size 1.27 1.27))))" +
- "(number \"2\" (effects (font (size 1.27 1.27)))))",
- "end_draw": "))"
- }
+ # Ensure directory exists
+ os.makedirs(esim_config_dir, exist_ok=True)
+
+ # Determine default workspace path
+ if os.name == 'nt':
+ user_home = os.path.join('library', 'config')
+ else:
+ user_home = os.path.expanduser('~')
+
+ default_workspace = os.path.join(user_home, 'eSim-Workspace')
+
+ # Create workspace directory if it doesn't exist
+ os.makedirs(default_workspace, exist_ok=True)
+
+ # Create properly formatted workspace.txt
+ # Format expected by Appconfig.py: "workspace_check home_path"
+ # workspace_check: 1 (workspace is set) or 0 (not set)
+ # home_path: path to the workspace directory
+ config_content = f"1 {default_workspace}\n"
+
+ with open(workspace_file, 'w') as f:
+ f.write(config_content)
+
+ print(f"✓ Created proper workspace configuration:")
+ print(f" Content: '{config_content.strip()}'")
+ print(f" Workspace directory: {default_workspace}")
+
+ return True
+
+ except Exception as e:
+ print(f"✗ Error creating workspace configuration: {e}")
+ return False
+
+def fix_appconfig_robustness():
+ """Provide suggestions for making Appconfig.py more robust"""
+
+ robust_code = '''
+# Suggested improvement for Appconfig.py around line 46:
+# Replace the problematic line with more robust parsing:
+
+# ORIGINAL (problematic):
+# workspace_check, home = file.readline().split(' ', 1)
+
+# IMPROVED (robust):
+try:
+ line = file.readline().strip()
+ if line:
+ parts = line.split(' ', 1)
+ if len(parts) >= 2:
+ workspace_check, home = parts[0], parts[1]
+ elif len(parts) == 1:
+ # Handle case where only workspace_check exists
+ workspace_check = parts[0]
+ home = os.path.join(os.path.expanduser('~'), 'eSim-Workspace')
+ print(f"Warning: Using default workspace path: {home}")
+ else:
+ # Handle empty line
+ workspace_check = '0'
+ home = os.path.join(os.path.expanduser('~'), 'eSim-Workspace')
+ print(f"Warning: Empty config, using defaults")
+ else:
+ # Handle empty file
+ workspace_check = '0'
+ home = os.path.join(os.path.expanduser('~'), 'eSim-Workspace')
+ print(f"Warning: Empty config file, using defaults")
+except Exception as e:
+ # Handle any other parsing errors
+ print(f"Error reading config: {e}")
+ workspace_check = '0'
+ home = os.path.join(os.path.expanduser('~'), 'eSim-Workspace')
+ print(f"Using default configuration")
+'''
+
+ print("\n" + "=" * 60)
+ print("SUGGESTED ROBUST CODE FIX FOR APPCONFIG.PY:")
+ print("=" * 60)
+ print(robust_code)
+
+def main():
+ """Main function to diagnose and fix configuration issues"""
+ print("eSim Configuration Diagnostic and Fix Tool")
+ print("=" * 45)
+
+ # Step 1: Diagnose current state
+ print("\n1. Diagnosing current configuration...")
+ is_valid, config_dir, workspace_file = diagnose_config_files()
+
+ # Step 2: Fix configuration if needed
+ if not is_valid:
+ print("\n2. Fixing configuration...")
+ success = create_proper_workspace_config(config_dir, workspace_file)
+ if success:
+ print("✓ Configuration fixed successfully")
+ else:
+ print("✗ Failed to fix configuration")
+ return
+ else:
+ print("\n2. Configuration appears to be valid")
+
+ # Step 3: Provide code improvement suggestions
+ print("\n3. Providing robustness improvements...")
+ fix_appconfig_robustness()
+
+ # Step 4: Final verification
+ print("\n4. Final verification...")
+ is_valid_final, _, _ = diagnose_config_files()
+
+ if is_valid_final:
+ print("\n" + "=" * 50)
+ print("SUCCESS: Configuration is now properly set up!")
+ print("=" * 50)
+ print("You can now try running: python Application.py")
+ print("\nIf you still get errors, consider applying the")
+ print("robust code fix to Appconfig.py as shown above.")
+ else:
+ print("\n" + "=" * 50)
+ print("ISSUE: Configuration still has problems")
+ print("=" * 50)
+ print("Please check file permissions and try again.")
+
+if __name__ == "__main__":
+ main()
diff --git a/src/maker/Maker.py b/src/maker/Maker.py
index b9895f21d..ad32761ac 100755
--- a/src/maker/Maker.py
+++ b/src/maker/Maker.py
@@ -26,520 +26,570 @@
# REVISION: Tuesday 25, January 2022
# =========================================================================
-# importing the files and libraries
-import hdlparse.verilog_parser as vlog
-from PyQt5 import QtCore, QtWidgets
-from PyQt5.QtCore import QThread
-from configuration.Appconfig import Appconfig
+# Import required libraries
import os
+import hdlparse.verilog_parser as vlog
import watchdog.events
import watchdog.observers
from os.path import expanduser
-home = expanduser("~")
-# import inotify.adapters
+from PyQt5 import QtCore, QtWidgets
+from PyQt5.QtCore import QThread
+from configuration.Appconfig import Appconfig
-# declaring the global variables
-# verilogfile stores the name of the file
-# toggle flag stores the object of the toggling button
+# Global variables
+home = expanduser("~")
verilogFile = []
toggle_flag = []
-# This function is called to accept TOS of makerchip
def makerchipTOSAccepted(display=True):
+ """
+ Function to accept Terms of Service of Makerchip
+
+ Args:
+ display (bool): Whether to display the dialog
+
+ Returns:
+ bool: True if TOS accepted, False otherwise
+ """
if not os.path.isfile(home + "/.makerchip_accepted"):
if display:
reply = QtWidgets.QMessageBox.warning(
- None, "Terms of Service", "Please review the Makerchip \
- Terms of Service \
- (\
- https://www.makerchip.com/terms/). \
- Have you read and do you \
- accept these Terms of Service?",
+ None,
+ "Terms of Service",
+ "Please review the Makerchip Terms of Service "
+ "("
+ "https://www.makerchip.com/terms/). "
+ "Have you read and do you accept these Terms of Service?",
QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No
)
-
+
if reply == QtWidgets.QMessageBox.Yes:
- f = open(home + "/.makerchip_accepted", "w")
- f.close()
+ with open(home + "/.makerchip_accepted", "w") as f:
+ f.close()
return True
-
return False
return True
-# beginning class Maker. This class create the Maker Tab
class Maker(QtWidgets.QWidget):
-
- # initailising the varaibles
- def __init__(self, filecount):
+ """
+ Main class for creating the Makerchip Tab widget
+ """
+
+ def __init__(self, filecount, is_dark_theme=False):
+ """
+ Initialize the Maker widget
+
+ Args:
+ filecount (int): File counter
+ is_dark_theme (bool): Whether to use dark theme
+ """
print(self)
-
+
QtWidgets.QWidget.__init__(self)
self.count = 0
self.text = ""
self.filecount = filecount
self.entry_var = {}
- self.createMakerWidget()
self.obj_Appconfig = Appconfig()
+ self.is_dark_theme = is_dark_theme
+
+ # Initialize components
+ self.createMakerWidget()
verilogFile.append("")
-
- # Creating the various components of the Widget(Maker Tab)
+
def createMakerWidget(self):
-
+ """Create the main widget layout"""
self.grid = QtWidgets.QGridLayout()
self.setLayout(self.grid)
-
- self.grid.addWidget(self.createoptionsBox(), 0, 0, QtCore.Qt.AlignTop)
- self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+
+ # Add spacing between widgets
+ self.grid.setVerticalSpacing(20)
+ self.grid.setContentsMargins(10, 10, 10, 10)
+
+ # Add options box at the top
+ self.grid.addWidget(self.createoptionsBox(), 0, 0)
+
+ # Add tlv file group below with proper spacing
+ self.grid.addWidget(self.creategroup(), 1, 0)
+
+ # Apply initial theme styling
+ self.apply_theme_styling()
+
self.show()
-
- # This function is to Add new verilog file
+
+ def apply_theme_styling(self):
+ """Apply theme styling to the Maker widget."""
+ self.setObjectName("maker_widget")
+ if self.is_dark_theme:
+ self.setStyleSheet("""
+ QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #23273a, stop:1 #181b24); color: #e8eaed; }
+ QGroupBox { border: 2px solid #40c4ff; border-radius: 14px; margin-top: 1em; padding: 15px; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #23273a, stop:1 #181b24); color: #e8eaed; }
+ QGroupBox::title { subcontrol-origin: margin; left: 15px; padding: 0 5px; color: #40c4ff; font-weight: bold; font-size: 14px; }
+ QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #40c4ff, stop:1 #1976d2); color: #181b24; border: 1px solid #40c4ff; min-height: 35px; min-width: 120px; padding: 8px 15px; border-radius: 10px; font-weight: 700; font-size: 12px; }
+ QPushButton:hover { background: #1976d2; color: #fff; border: 1.5px solid #1976d2; }
+ QPushButton:pressed { background: #23273a; color: #40c4ff; border: 1.5px solid #40c4ff; }
+ QPushButton:disabled { background: #23273a; color: #888; border: 1px solid #23273a; }
+ QTextEdit { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; padding: 10px; font-size: 12px; font-family: 'Consolas', 'Monaco', monospace; }
+ QLineEdit { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; padding: 8px 12px; min-height: 30px; font-size: 12px; }
+ QLineEdit:focus { border: 1.5px solid #1976d2; }
+ QLabel { color: #e8eaed; }
+ """)
+ else:
+ self.setStyleSheet("""
+ QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #ffffff, stop:1 #f8f9fa); color: #2c3e50; }
+ QGroupBox { border: 2px solid #1976d2; border-radius: 14px; margin-top: 1em; padding: 15px; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #ffffff, stop:1 #f8f9fa); color: #2c3e50; }
+ QGroupBox::title { subcontrol-origin: margin; left: 15px; padding: 0 5px; color: #1976d2; font-weight: bold; font-size: 14px; }
+ QPushButton {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #f5f7fa, stop:1 #e3e8ee);
+ color: #1976d2;
+ border: 1px solid #b0bec5;
+ min-height: 35px;
+ min-width: 120px;
+ padding: 8px 15px;
+ border-radius: 10px;
+ font-weight: 700;
+ font-size: 12px;
+ }
+ QPushButton:hover {
+ background: #e3e8ee;
+ color: #1565c0;
+ border: 1.5px solid #1976d2;
+ }
+ QPushButton:pressed {
+ background: #cfd8dc;
+ color: #1976d2;
+ border: 1.5px solid #1976d2;
+ }
+ QPushButton:disabled {
+ background: #e1e4e8;
+ color: #7f8c8d;
+ border: 1px solid #e1e4e8;
+ }
+ QTextEdit { background: #ffffff; color: #2c3e50; border: 1px solid #1976d2; border-radius: 8px; padding: 10px; font-size: 12px; font-family: 'Consolas', 'Monaco', monospace; }
+ QLineEdit { background: #ffffff; color: #2c3e50; border: 1px solid #1976d2; border-radius: 8px; padding: 8px 12px; min-height: 30px; font-size: 12px; }
+ QLineEdit:focus { border: 1.5px solid #1565c0; }
+ QLabel { color: #2c3e50; }
+ """)
+
def addverilog(self):
-
- init_path = '../../'
- if os.name == 'nt':
- init_path = ''
+ """Add new Verilog file to the widget"""
+ init_path = '../../' if os.name != 'nt' else ''
+
self.verilogfile = QtCore.QDir.toNativeSeparators(
QtWidgets.QFileDialog.getOpenFileName(
- self, "Open Verilog Directory",
- init_path + "home", "*v"
+ self,
+ "Open Verilog Directory",
+ init_path + "home",
+ "*v"
)[0]
)
+
if self.verilogfile == "":
self.verilogfile = self.entry_var[0].text()
-
+
if self.verilogfile == "":
reply = QtWidgets.QMessageBox.critical(
None,
"Error Message",
- "No Verilog File Chosen. \
- Please choose a verilog file.",
- QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel)
-
+ "No Verilog File Chosen. Please choose a verilog file.",
+ QtWidgets.QMessageBox.Ok | QtWidgets.QMessageBox.Cancel
+ )
+
if reply == QtWidgets.QMessageBox.Ok:
self.addverilog()
-
if self.verilogfile == "":
return
-
self.obj_Appconfig.print_info('Add Verilog File Called')
-
+
elif reply == QtWidgets.QMessageBox.Cancel:
self.obj_Appconfig.print_info('No Verilog File Chosen')
return
-
+
+ # Read and set file content
self.text = open(self.verilogfile).read()
self.entry_var[0].setText(self.verilogfile)
self.entry_var[1].setText(self.text)
+
global verilogFile
-
verilogFile[self.filecount] = self.verilogfile
+
+ # Setup file watching
+ self._setup_file_watcher()
+
+ def _setup_file_watcher(self):
+ """Setup file watcher for automatic refresh"""
if self.refreshoption in toggle_flag:
toggle_flag.remove(self.refreshoption)
-
+
self.observer = watchdog.observers.Observer()
self.event_handler = Handler(
self.verilogfile,
self.refreshoption,
- self.observer)
-
+ self.observer
+ )
+
self.observer.schedule(
self.event_handler,
path=self.verilogfile,
- recursive=True)
+ recursive=True
+ )
self.observer.start()
- # self.notify=notify(self.verilogfile,self.refreshoption)
- # self.notify.start()
- # open("filepath.txt","w").write(self.verilogfile)
-
- # This function is used to call refresh while
- # running Ngspice to Verilog Converter
- # (as the original one gets destroyed)
+
def refresh_change(self):
+ """
+ Call refresh while running Ngspice to Verilog Converter
+ (as the original one gets destroyed)
+ """
if self.refreshoption in toggle_flag:
self.toggle = toggle(self.refreshoption)
self.toggle.start()
-
- # It is used to refresh the file in eSim if its edited anywhere else
+
def refresh(self):
+ """Refresh the file content if edited elsewhere"""
if not hasattr(self, 'verilogfile'):
return
+
self.text = open(self.verilogfile).read()
self.entry_var[1].setText(self.text)
+
print("NgVeri File: " + self.verilogfile + " Refreshed")
self.obj_Appconfig.print_info(
- "NgVeri File: " + self.verilogfile + " Refreshed")
- self.observer = watchdog.observers.Observer()
- self.event_handler = Handler(
- self.verilogfile,
- self.refreshoption,
- self.observer)
-
- self.observer.schedule(
- self.event_handler,
- path=self.verilogfile,
- recursive=True)
- self.observer.start()
- # self.notify.start()
+ "NgVeri File: " + self.verilogfile + " Refreshed"
+ )
+
+ # Restart file watcher
+ self._setup_file_watcher()
+
global toggle_flag
if self.refreshoption in toggle_flag:
toggle_flag.remove(self.refreshoption)
-
- # This function is used to save the edited file in eSim
+
def save(self):
+ """Save the edited file"""
try:
wr = self.entry_var[1].toPlainText()
- open(self.verilogfile, "w+").write(wr)
+ with open(self.verilogfile, "w+") as f:
+ f.write(wr)
except BaseException as err:
- self.msg = QtWidgets.QErrorMessage(self)
- self.msg.setModal(True)
- self.msg.setWindowTitle("Error Message")
- self.msg.showMessage(
+ self._show_error_message(
"Error in saving verilog file. Please check if it is chosen."
)
- self.msg.exec_()
print("Error in saving verilog file: " + str(err))
-
- # This is used to run the makerchip-app
+
+ def _show_error_message(self, message):
+ """Show error message dialog"""
+ self.msg = QtWidgets.QErrorMessage(self)
+ self.msg.setModal(True)
+ self.msg.setWindowTitle("Error Message")
+ self.msg.showMessage(message)
+ self.msg.exec_()
+
def runmakerchip(self):
- init_path = '../../'
- if os.name == 'nt':
- init_path = ''
+ """Run the Makerchip IDE"""
+ init_path = '../../' if os.name != 'nt' else ''
+
try:
if not makerchipTOSAccepted(True):
return
-
+
print("Running Makerchip IDE...........................")
- # self.file = open(self.verilogfile,"w")
- # self.file.write(self.entry_var[1].toPlainText())
- # self.file.close()
filename = self.verilogfile
+
if self.verilogfile.split('.')[-1] != "tlv":
- reply = QtWidgets.QMessageBox.warning(
- None,
- "Do you want to automate the top module? ",
- "Click on YES button if you want the top module \
- to be added automatically. A .tlv file will be created \
- in the directory of current verilog file \
- and the Makerchip IDE will be running on \
- this file. Otherwise click on NO button. \
- To not open Makerchip IDE, click on CANCEL button. \
-
NOTE: Makerchip IDE requires an active \
- internet connection and a browser.",
- QtWidgets.QMessageBox.Yes
- | QtWidgets.QMessageBox.No
- | QtWidgets.QMessageBox.Cancel)
+ reply = self._show_automation_dialog()
+
if reply == QtWidgets.QMessageBox.Cancel:
return
+
if reply == QtWidgets.QMessageBox.Yes:
- code = open(self.verilogfile).read()
- text = code
- filename = '.'.join(
- self.verilogfile.split('.')[:-1]) + ".tlv"
- file = os.path.basename('.'.join(
- self.verilogfile.split('.')[:-1]))
- f = open(filename, 'w')
- code = code.replace(" wire ", " ")
- code = code.replace(" reg ", " ")
- vlog_ex = vlog.VerilogExtractor()
- vlog_mods = vlog_ex.extract_objects_from_source(code)
- lint_off = open(
- init_path + "library/tlv/lint_off.txt"
- ).readlines()
- string = '''\\TLV_version 1d: tl-x.org\n\\SV\n'''
- for item in lint_off:
- string += "/* verilator lint_off " + \
- item.strip("\n") + "*/ "
- string += '''\n\n//Your Verilog/System \
-Verilog Code Starts Here:\n''' + \
- text + '''\n\n//Top Module Code \
-Starts here:\n\tmodule top(input \
-logic clk, input logic reset, input logic [31:0] cyc_cnt, \
-output logic passed, output logic failed);\n'''
- print(file)
- for m in vlog_mods:
- if m.name.lower() == file.lower():
- for p in m.ports:
- if str(
- p.name) != "clk" and str(
- p.name) != "reset" and str(
- p.name) != "cyc_cnt" and str(
- p.name) != "passed" and str(
- p.name) != "failed":
- string += '\t\tlogic ' + p.data_type\
- + " " + p.name + ";//" + p.mode + "\n"
- if m.name.lower() != file.lower():
- QtWidgets.QMessageBox.critical(
- None,
- "Error Message",
- "Error: File name and module \
- name are not same. Please \
- ensure that they are same.",
- QtWidgets.QMessageBox.Ok)
-
- self.obj_Appconfig.print_info(
- 'NgVeri stopped due to file \
-name and module name not matching error')
+ filename = self._process_verilog_to_tlv(init_path)
+ if filename is None:
return
- string += "//The $random() can be replaced \
-if user wants to assign values\n"
- for m in vlog_mods:
- if m.name.lower() == file.lower():
- for p in m.ports:
- if str(
- p.mode) == "input" or str(
- p.mode) == "inout":
- if str(
- p.name) != "clk" and str(
- p.name) != "reset" and str(
- p.name) != "cyc_cnt" and str(
- p.name) != "passed" and str(
- p.name) != "failed":
- string += '\t\tassign ' + p.name\
- + " = " + "$random();\n"
-
- for m in vlog_mods:
- if m.name.lower() == file.lower():
- string += '\t\t' + m.name + " " + m.name + '('
- i = 0
- for p in m.ports:
- i = i + 1
- string += "."+p.name+"("+p.name+")"
- if i == len(m.ports):
- string += ");\n\t\n\\TLV\n//\
-Add \\TLV here if desired\
- \n\\SV\nendmodule\n\n"
- else:
- string += ", "
- f.write(string)
-
+
+ # Start Makerchip process
self.process = QtCore.QProcess(self)
cmd = 'makerchip ' + filename
print("File: " + filename)
self.process.start(cmd)
- print(
- "Makerchip IDE command process pid ---------->",
- self.process.pid())
+ print("Makerchip IDE command process pid ---------->", self.process.pid())
+
except BaseException as e:
print(e)
- self.msg = QtWidgets.QErrorMessage(self)
- self.msg.setModal(True)
- self.msg.setWindowTitle("Error Message")
- self.msg.showMessage(
- "Error in running Makerchip IDE. \
-Please check if verilog file is chosen.")
- self.msg.exec_()
- print("Error in running Makerchip IDE. \
-Please check if verilog file is chosen.")
- # initial = self.read_file()
-
- # while True:
- # current = self.read_file()
- # if initial != current:
- # for line in current:
- # if line not in initial:
- # print(line)
- # initial = current
- # self.processfile = QtCore.QProcess(self)
- # self.processfile.start("python3 notify.py")
- # print(self.processfile.readChannel())
-
- # This creates the buttons/options
-
+ self._show_error_message(
+ "Error in running Makerchip IDE. Please check if verilog file is chosen."
+ )
+ print("Error in running Makerchip IDE. Please check if verilog file is chosen.")
+
+ def _show_automation_dialog(self):
+ """Show automation confirmation dialog"""
+ return QtWidgets.QMessageBox.warning(
+ None,
+ "Do you want to automate the top module? ",
+ "Click on YES button if you want the top module to be added automatically. "
+ "A .tlv file will be created in the directory of current verilog file "
+ "and the Makerchip IDE will be running on this file. Otherwise click on NO button. "
+ "To not open Makerchip IDE, click on CANCEL button.
"
+ "NOTE: Makerchip IDE requires an active internet connection and a browser.",
+ QtWidgets.QMessageBox.Yes | QtWidgets.QMessageBox.No | QtWidgets.QMessageBox.Cancel
+ )
+
+ def _process_verilog_to_tlv(self, init_path):
+ """Process Verilog file to TLV format"""
+ code = open(self.verilogfile).read()
+ text = code
+ filename = '.'.join(self.verilogfile.split('.')[:-1]) + ".tlv"
+ file = os.path.basename('.'.join(self.verilogfile.split('.')[:-1]))
+
+ # Process Verilog code
+ code = code.replace(" wire ", " ")
+ code = code.replace(" reg ", " ")
+
+ vlog_ex = vlog.VerilogExtractor()
+ vlog_mods = vlog_ex.extract_objects_from_source(code)
+
+ # Read lint_off file
+ lint_off = open(init_path + "library/tlv/lint_off.txt").readlines()
+
+ # Generate TLV content
+ string = self._generate_tlv_content(lint_off, text, file, vlog_mods)
+
+ # Validate module name
+ if not self._validate_module_name(file, vlog_mods):
+ return None
+
+ # Write TLV file
+ with open(filename, 'w') as f:
+ f.write(string)
+
+ return filename
+
+ def _generate_tlv_content(self, lint_off, text, file, vlog_mods):
+ """Generate TLV file content"""
+ string = '''\\TLV_version 1d: tl-x.org\n\\SV\n'''
+
+ # Add lint_off directives
+ for item in lint_off:
+ string += "/* verilator lint_off " + item.strip("\n") + "*/ "
+
+ string += '''\n\n//Your Verilog/System Verilog Code Starts Here:\n''' + text
+ string += '''\n\n//Top Module Code Starts here:\n\tmodule top(input logic clk, '''
+ string += '''input logic reset, input logic [31:0] cyc_cnt, '''
+ string += '''output logic passed, output logic failed);\n'''
+
+ print(file)
+
+ # Add port declarations
+ for m in vlog_mods:
+ if m.name.lower() == file.lower():
+ for p in m.ports:
+ if str(p.name) not in ["clk", "reset", "cyc_cnt", "passed", "failed"]:
+ string += '\t\tlogic ' + p.data_type + " " + p.name + ";//" + p.mode + "\n"
+
+ # Add random assignments
+ string += "//The $random() can be replaced if user wants to assign values\n"
+ for m in vlog_mods:
+ if m.name.lower() == file.lower():
+ for p in m.ports:
+ if str(p.mode) in ["input", "inout"]:
+ if str(p.name) not in ["clk", "reset", "cyc_cnt", "passed", "failed"]:
+ string += '\t\tassign ' + p.name + " = " + "$random();\n"
+
+ # Add module instantiation
+ for m in vlog_mods:
+ if m.name.lower() == file.lower():
+ string += '\t\t' + m.name + " " + m.name + '('
+ i = 0
+ for p in m.ports:
+ i = i + 1
+ string += "." + p.name + "(" + p.name + ")"
+ if i == len(m.ports):
+ string += ");\n\t\n\\TLV\n//Add \\TLV here if desired\n\\SV\nendmodule\n\n"
+ else:
+ string += ", "
+
+ return string
+
+ def _validate_module_name(self, file, vlog_mods):
+ """Validate that file name matches module name"""
+ for m in vlog_mods:
+ if m.name.lower() != file.lower():
+ QtWidgets.QMessageBox.critical(
+ None,
+ "Error Message",
+ "Error: File name and module name are not same. "
+ "Please ensure that they are same.",
+ QtWidgets.QMessageBox.Ok
+ )
+ self.obj_Appconfig.print_info(
+ 'NgVeri stopped due to file name and module name not matching error'
+ )
+ return False
+ return True
+
def createoptionsBox(self):
-
+ """Create the options/buttons box"""
self.optionsbox = QtWidgets.QGroupBox()
self.optionsbox.setTitle("Select Options")
self.optionsgrid = QtWidgets.QGridLayout()
- # self.optionsbox2 = QtWidgets.QGroupBox()
- # self.optionsbox2.setTitle("Note: Please save the file once edited")
- # self.optionsgrid2 = QtWidgets.QGridLayout()
self.optionsgroupbtn = QtWidgets.QButtonGroup()
+
+ # Set margins and spacing for the options grid
+ self.optionsgrid.setContentsMargins(15, 20, 15, 15)
+ self.optionsgrid.setSpacing(15)
+
+ # Add Top Level Verilog Model button
self.addoptions = QtWidgets.QPushButton("Add Top Level Verilog Model")
self.optionsgroupbtn.addButton(self.addoptions)
self.addoptions.clicked.connect(self.addverilog)
- self.optionsgrid.addWidget(self.addoptions, 0, 1)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0
+ self.optionsgrid.addWidget(self.addoptions, 0, 0)
+
+ # Refresh button
self.refreshoption = QtWidgets.QPushButton("Refresh")
self.optionsgroupbtn.addButton(self.refreshoption)
self.refreshoption.clicked.connect(self.refresh)
- self.optionsgrid.addWidget(self.refreshoption, 0, 2)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+ self.optionsgrid.addWidget(self.refreshoption, 0, 1)
+
+ # Save button
self.saveoption = QtWidgets.QPushButton("Save")
self.optionsgroupbtn.addButton(self.saveoption)
self.saveoption.clicked.connect(self.save)
- self.optionsgrid.addWidget(self.saveoption, 0, 3)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+ self.optionsgrid.addWidget(self.saveoption, 0, 2)
+
+ # Edit in Makerchip IDE button
self.runoptions = QtWidgets.QPushButton("Edit in Makerchip IDE")
- self.runoptions.setToolTip(
- "Requires internet connection and a browser"
- )
+ self.runoptions.setToolTip("Requires internet connection and a browser")
self.runoptions.setToolTipDuration(5000)
self.optionsgroupbtn.addButton(self.runoptions)
self.runoptions.clicked.connect(self.runmakerchip)
- self.optionsgrid.addWidget(self.runoptions, 0, 4)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+ self.optionsgrid.addWidget(self.runoptions, 0, 3)
+
+ # Accept TOS button (if needed)
if not makerchipTOSAccepted(False):
self.acceptTOS = QtWidgets.QPushButton("Accept Makerchip TOS")
self.optionsgroupbtn.addButton(self.acceptTOS)
self.acceptTOS.clicked.connect(lambda: makerchipTOSAccepted(True))
- self.optionsgrid.addWidget(self.acceptTOS, 0, 5)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+ self.optionsgrid.addWidget(self.acceptTOS, 0, 4)
+
self.optionsbox.setLayout(self.optionsgrid)
return self.optionsbox
-
- # This function adds the other parts of widget like text box
+
def creategroup(self):
+ """Create the text editor group"""
self.trbox = QtWidgets.QGroupBox()
self.trbox.setTitle(".tlv file")
- # self.trbox.setDisabled(True)
- # self.trbox.setVisible(False)
self.trgrid = QtWidgets.QGridLayout()
- self.trbox.setLayout(self.trgrid)
-
+
+ # Set margins and spacing for the tlv grid
+ self.trgrid.setContentsMargins(10, 15, 10, 10)
+ self.trgrid.setSpacing(10)
+
+ # Path label and field
self.start = QtWidgets.QLabel("Path to .tlv file")
- self.trgrid.addWidget(self.start, 1, 0)
+ self.trgrid.addWidget(self.start, 0, 0)
+
self.count = 0
self.entry_var[self.count] = QtWidgets.QLabel()
- self.trgrid.addWidget(self.entry_var[self.count], 1, 1)
+ self.trgrid.addWidget(self.entry_var[self.count], 0, 1)
self.entry_var[self.count].setMaximumWidth(1000)
self.count += 1
-
- # CSS
- self.trbox.setStyleSheet(" \
- QGroupBox { border: 1px solid gray; border-radius: \
- 9px; margin-top: 0.5em; } \
- QGroupBox::title { subcontrol-origin: margin; left: \
- 10px; padding: 0 3px 0 3px; } \
- ")
-
+
+ # Code editor
self.start = QtWidgets.QLabel(".tlv code")
- # self.start2 = QtWidgets.QLabel("Note: \
- # Please save the file once edited")
- # self.start2.setStyleSheet("background-color: red")
- self.trgrid.addWidget(self.start, 2, 0)
- # self.trgrid.addWidget(self.start2, 3,0)
+ self.trgrid.addWidget(self.start, 1, 0)
+
self.entry_var[self.count] = QtWidgets.QTextEdit()
- self.trgrid.addWidget(self.entry_var[self.count], 2, 1)
+ self.trgrid.addWidget(self.entry_var[self.count], 1, 1)
self.entry_var[self.count].setMaximumWidth(1000)
- self.entry_var[self.count].setMaximumHeight(1000)
- # self.entry_var[self.count].textChanged.connect(self.save)
+ self.entry_var[self.count].setMinimumHeight(300) # Set minimum height
self.count += 1
-
- # CSS
- self.trbox.setStyleSheet(" \
- QGroupBox { border: 1px solid gray; border-radius: \
- 9px; margin-top: 0.5em; } \
- QGroupBox::title { subcontrol-origin: margin; left: \
- 10px; padding: 0 3px 0 3px; } \
- ")
-
+
+ self.trbox.setLayout(self.trgrid)
return self.trbox
+ def set_theme(self, is_dark_theme):
+ """Update the theme and re-apply styling."""
+ self.is_dark_theme = is_dark_theme
+ self.apply_theme_styling()
+
-# The Handler class is used to create a watch on the files using WatchDog
class Handler(watchdog.events.PatternMatchingEventHandler):
- # this function initialisses the variable and the objects of watchdog
+ """
+ Handler class for file watching using WatchDog
+ """
+
def __init__(self, verilogfile, refreshoption, observer):
- # Set the patterns for PatternMatchingEventHandler
+ """
+ Initialize the file handler
+
+ Args:
+ verilogfile (str): Path to the Verilog file
+ refreshoption (QPushButton): Refresh button reference
+ observer (Observer): File observer instance
+ """
watchdog.events.PatternMatchingEventHandler.__init__(
- self, ignore_directories=True, case_sensitive=False)
+ self,
+ ignore_directories=True,
+ case_sensitive=False
+ )
self.verilogfile = verilogfile
self.refreshoption = refreshoption
self.obj_Appconfig = Appconfig()
self.observer = observer
self.toggle = toggle(self.refreshoption)
-
- # if a file is modified, toggle starts to toggle the refresh button
+
def on_modified(self, event):
- print("Watchdog received modified event - % s." % event.src_path)
+ """Handle file modification events"""
+ print("Watchdog received modified event - %s." % event.src_path)
+
msg = QtWidgets.QErrorMessage()
msg.setWindowTitle("eSim Message")
msg.showMessage(
- "NgVeri File: " +
- self.verilogfile +
- " modified. Please click on Refresh")
+ "NgVeri File: " + self.verilogfile + " modified. Please click on Refresh"
+ )
msg.exec_()
- print("NgVeri File: " + self.verilogfile +
- " modified. Please click on Refresh")
- # self.obj_Appconfig.print_info("NgVeri File:\
- # "+self.verilogfile+" modified. Please click on Refresh")
+
+ print("NgVeri File: " + self.verilogfile + " modified. Please click on Refresh")
+
global toggle_flag
if self.refreshoption not in toggle_flag:
toggle_flag.append(self.refreshoption)
- # i.rm_watch()
+
self.observer.stop()
self.toggle.start()
-# class notify(QThread):
-# def __init__(self,verilogfile,refreshoption):#,obj_Appconfig):
-# QThread.__init__(self)
-# self.verilogfile=verilogfile
-# self.refreshoption=refreshoption
-# self.obj_Appconfig = Appconfig()
-# self.toggle=toggle(self.refreshoption)
-
-
-# def __del__(self):
-# self.wait()
-
-# def run(self):
-# i = inotify.adapters.Inotify()
-
-# i.add_watch(self.verilogfile)
-
-# for event in i.event_gen():
-# if not self.refreshoption.isVisible():
-# break
-# if event!=None:
-# print(event)
-# if "IN_CLOSE_WRITE" in event[1] :
-# msg = QtWidgets.QErrorMessage()
-# msg.setModal(True)
-# msg.setWindowTitle("eSim Message")
-# msg.showMessage(
-# "NgVeri File: "+self.verilogfile+"\
-# modified. Please click on Refresh")
-# msg.exec_()
-# print("NgVeri File: "+self.verilogfile+"\
-# modified. Please click on Refresh")
-# # self.obj_Appconfig.print_info("NgVeri File: \
-# "+self.verilogfile+" modified. Please click on Refresh")
-# global toggle_flag
-# toggle_flag.append(self.refreshoption)
-# #i.rm_watch()
-# self.toggle.start()
-# break
-
-
-# This class is used to toggle a button(change colour by toggling)
class toggle(QThread):
- # initialising the threads
+ """
+ Class to toggle button appearance (change color by toggling)
+ """
+
def __init__(self, option):
+ """
+ Initialize the toggle thread
+
+ Args:
+ option (QPushButton): Button to toggle
+ """
QThread.__init__(self)
self.option = option
-
+
def __del__(self):
+ """Clean up the thread"""
self.wait()
-
- # running the thread to toggle
+
def run(self):
-
+ """Run the toggle thread"""
while True:
self.option.setStyleSheet("background-color: red")
self.sleep(1)
self.option.setStyleSheet("background-color: none")
self.sleep(1)
+
print(toggle_flag)
+
if not self.option.isVisible():
break
if self.option not in toggle_flag:
- break
+ break
\ No newline at end of file
diff --git a/src/maker/ModelGeneration.py b/src/maker/ModelGeneration.py
index f6afd5c00..7dce1de70 100755
--- a/src/maker/ModelGeneration.py
+++ b/src/maker/ModelGeneration.py
@@ -167,12 +167,6 @@ def verilogParse(self):
code = code.replace("wire", " ")
code = code.replace("reg", " ")
-
- header_re = re.compile(r'module\s+\w+\s*\((.*?)\)\s*;', re.S)
- def _split_ports(match):
- # add a newline after every comma that is inside the header
- return match.group(0).replace(',', ',\n')
- code = header_re.sub(_split_ports, code)
vlog_ex = vlog.VerilogExtractor()
vlog_mods = vlog_ex.extract_objects_from_source(code)
f = open(self.modelpath + "connection_info.txt", 'w')
@@ -724,7 +718,7 @@ def sim_main(self):
int foo_''' + self.fname.split('.')[0] + '''(int init,int count)
{
int argc=1;
- const char* argv[]={"fullverbose"};
+ char* argv[]={"fullverbose"};
Verilated::commandArgs(argc, argv);
static VerilatedContext* contextp = new VerilatedContext;
static V''' + self.fname.split('.')[0] + "* " + \
diff --git a/src/maker/NgVeri.py b/src/maker/NgVeri.py
index 1678248fe..8cbd17e94 100755
--- a/src/maker/NgVeri.py
+++ b/src/maker/NgVeri.py
@@ -41,10 +41,11 @@ class NgVeri(QtWidgets.QWidget):
'''
This class create the NgVeri Tab
'''
- def __init__(self, filecount):
+ def __init__(self, filecount, is_dark_theme=False):
QtWidgets.QWidget.__init__(self)
# Maker.addverilog(self)
self.obj_Appconfig = Appconfig()
+ self.is_dark_theme = is_dark_theme
if os.name == 'nt':
self.home = os.path.join('library', 'config')
@@ -77,8 +78,74 @@ def createNgveriWidget(self):
self.grid.addWidget(self.createoptionsBox(), 0, 0, QtCore.Qt.AlignTop)
self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+ # Apply initial theme styling
+ self.apply_theme_styling()
+
self.show()
+ def apply_theme_styling(self):
+ """Apply theme styling to the NgVeri widget."""
+ self.setObjectName("ngveri_widget")
+
+ if self.is_dark_theme:
+ self.setStyleSheet("""
+ QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #23273a, stop:1 #181b24); color: #e8eaed; }
+ QGroupBox { border: 2px solid #40c4ff; border-radius: 14px; margin-top: 1em; padding: 15px; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #23273a, stop:1 #181b24); color: #e8eaed; }
+ QGroupBox::title { subcontrol-origin: margin; left: 15px; padding: 0 5px; color: #40c4ff; font-weight: bold; font-size: 14px; }
+ QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #40c4ff, stop:1 #1976d2); color: #181b24; border: 1px solid #40c4ff; min-height: 35px; min-width: 120px; padding: 8px 15px; border-radius: 10px; font-weight: 700; font-size: 12px; }
+ QPushButton:hover { background: #1976d2; color: #fff; border: 1.5px solid #1976d2; }
+ QPushButton:pressed { background: #23273a; color: #40c4ff; border: 1.5px solid #40c4ff; }
+ QPushButton:disabled { background: #23273a; color: #888; border: 1px solid #23273a; }
+ QTextEdit { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; padding: 10px; font-size: 12px; font-family: 'Consolas', 'Monaco', monospace; }
+ QComboBox { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; padding: 5px 10px; min-height: 30px; font-size: 12px; }
+ QComboBox:hover { border: 1.5px solid #1976d2; }
+ QComboBox::drop-down { border: none; width: 20px; }
+ QComboBox::down-arrow { width: 12px; height: 12px; }
+ QLineEdit { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; padding: 8px 12px; min-height: 30px; font-size: 12px; }
+ QLineEdit:focus { border: 1.5px solid #1976d2; }
+ QLabel { color: #e8eaed; }
+ """)
+ else:
+ self.setStyleSheet("""
+ QWidget { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #ffffff, stop:1 #f8f9fa); color: #2c3e50; }
+ QGroupBox { border: 2px solid #1976d2; border-radius: 14px; margin-top: 1em; padding: 15px; background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #ffffff, stop:1 #f8f9fa); color: #2c3e50; }
+ QGroupBox::title { subcontrol-origin: margin; left: 15px; padding: 0 5px; color: #1976d2; font-weight: bold; font-size: 14px; }
+ QPushButton {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #f5f7fa, stop:1 #e3e8ee);
+ color: #1976d2;
+ border: 1px solid #b0bec5;
+ min-height: 35px;
+ min-width: 120px;
+ padding: 8px 15px;
+ border-radius: 10px;
+ font-weight: 700;
+ font-size: 12px;
+ }
+ QPushButton:hover {
+ background: #e3e8ee;
+ color: #1565c0;
+ border: 1.5px solid #1976d2;
+ }
+ QPushButton:pressed {
+ background: #cfd8dc;
+ color: #1976d2;
+ border: 1.5px solid #1976d2;
+ }
+ QPushButton:disabled {
+ background: #e1e4e8;
+ color: #7f8c8d;
+ border: 1px solid #e1e4e8;
+ }
+ QTextEdit { background: #ffffff; color: #2c3e50; border: 1px solid #1976d2; border-radius: 8px; padding: 10px; font-size: 12px; font-family: 'Consolas', 'Monaco', monospace; }
+ QComboBox { background: #ffffff; color: #2c3e50; border: 1px solid #1976d2; border-radius: 8px; padding: 5px 10px; min-height: 30px; font-size: 12px; }
+ QComboBox:hover { border: 1.5px solid #1565c0; }
+ QComboBox::drop-down { border: none; width: 20px; }
+ QComboBox::down-arrow { width: 12px; height: 12px; }
+ QLineEdit { background: #ffffff; color: #2c3e50; border: 1px solid #1976d2; border-radius: 8px; padding: 8px 12px; min-height: 30px; font-size: 12px; }
+ QLineEdit:focus { border: 1.5px solid #1565c0; }
+ QLabel { color: #2c3e50; }
+ """)
+
def addverilog(self):
'''
Adding the verilog file in Maker tab to Ngveri Tab automatically
@@ -231,9 +298,12 @@ def createoptionsBox(self):
self.optionsbox = QtWidgets.QGroupBox()
self.optionsbox.setTitle("Select Options")
self.optionsgrid = QtWidgets.QGridLayout()
-
self.optionsgroupbtn = QtWidgets.QButtonGroup()
+ # Set margins and spacing for better layout
+ self.optionsgrid.setContentsMargins(15, 20, 15, 15)
+ self.optionsgrid.setSpacing(15)
+
self.addverilogbutton = QtWidgets.QPushButton(
"Convert Verilog to Ngspice")
self.addverilogbutton.setToolTip(
@@ -243,30 +313,23 @@ def createoptionsBox(self):
self.optionsgroupbtn.addButton(self.addverilogbutton)
self.addverilogbutton.clicked.connect(self.addverilog)
self.optionsgrid.addWidget(self.addverilogbutton, 0, 1)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
self.addfilebutton = QtWidgets.QPushButton("Add dependency files")
self.optionsgroupbtn.addButton(self.addfilebutton)
self.addfilebutton.clicked.connect(self.addfile)
self.optionsgrid.addWidget(self.addfilebutton, 0, 2)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
self.addfolderbutton = QtWidgets.QPushButton("Add dependency folder")
self.optionsgroupbtn.addButton(self.addfolderbutton)
self.addfolderbutton.clicked.connect(self.addfolder)
self.optionsgrid.addWidget(self.addfolderbutton, 0, 3)
- # self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
self.clearTerminalBtn = QtWidgets.QPushButton("Clear Terminal")
self.optionsgroupbtn.addButton(self.clearTerminalBtn)
self.clearTerminalBtn.clicked.connect(self.clearTerminal)
self.optionsgrid.addWidget(self.clearTerminalBtn, 0, 4)
- self.optionsbox.setLayout(self.optionsgrid)
- # self.grid.addWidget(self.creategroup(), 1, 0, 5, 0)
+ self.optionsbox.setLayout(self.optionsgrid)
return self.optionsbox
def edit_modlst(self, text):
@@ -338,21 +401,14 @@ def lint_off_edit(self, text):
QtWidgets.QMessageBox.Cancel)
if ret == QtWidgets.QMessageBox.Ok:
- try:
- file_path = os.path.join(init_path, "library/tlv/lint_off.txt")
- with open(file_path, 'r') as file:
- data = file.readlines()
- data = [line for line in data if line.strip() != text]
- with open(file_path, 'w') as file:
- file.writelines(data)
-
- except Exception as e:
- QtWidgets.QMessageBox.warning(
- None,
- "Warning",
- f"Could not remove lint_off entry '{text}'",
- QtWidgets.QMessageBox.Ok
- )
+ file = open(init_path + "library/tlv/lint_off.txt", 'r')
+ data = file.readlines()
+ file.close()
+
+ data.remove(text + "\n")
+ file = open(init_path + "library/tlv/lint_off.txt", 'w')
+ for item in data:
+ file.write(item)
def add_lint_off(self):
'''
@@ -379,14 +435,15 @@ def creategroup(self):
'''
self.trbox = QtWidgets.QGroupBox()
self.trbox.setTitle("Terminal")
- # self.trbox.setDisabled(True)
- # self.trbox.setVisible(False)
self.trgrid = QtWidgets.QGridLayout()
self.trbox.setLayout(self.trgrid)
self.count = 0
+ # Set margins and spacing for better layout
+ self.trgrid.setContentsMargins(15, 20, 15, 15)
+ self.trgrid.setSpacing(15)
+
self.start = QtWidgets.QLabel("Terminal")
- # self.trgrid.addWidget(self.start, 2,0)
self.entry_var[self.count] = QtWidgets.QTextEdit()
self.entry_var[self.count].setReadOnly(1)
self.trgrid.addWidget(self.entry_var[self.count], 1, 1, 5, 3)
@@ -405,6 +462,7 @@ def creategroup(self):
self.entry_var[self.count].activated[str].connect(self.edit_modlst)
self.trgrid.addWidget(self.entry_var[self.count], 1, 4, 1, 2)
self.count += 1
+
self.entry_var[self.count] = QtWidgets.QComboBox()
self.entry_var[self.count].addItem("Remove lint_off")
@@ -421,23 +479,23 @@ def creategroup(self):
self.entry_var[self.count].activated[str].connect(self.lint_off_edit)
self.trgrid.addWidget(self.entry_var[self.count], 2, 4, 1, 2)
self.count += 1
+
self.entry_var[self.count] = QtWidgets.QLineEdit(self)
self.trgrid.addWidget(self.entry_var[self.count], 3, 4)
- self.entry_var[self.count].setMaximumWidth(100)
+ self.entry_var[self.count].setMaximumWidth(200)
self.count += 1
+
self.entry_var[self.count] = QtWidgets.QPushButton("Add lint_off")
- self.entry_var[self.count].setMaximumWidth(100)
+ self.entry_var[self.count].setMaximumWidth(150)
self.trgrid.addWidget(self.entry_var[self.count], 3, 5)
self.entry_var[self.count].clicked.connect(self.add_lint_off)
self.count += 1
- # CSS
- self.trbox.setStyleSheet(" \
- QGroupBox { border: 1px solid gray; border-radius: \
- 9px; margin-top: 0.5em; } \
- QGroupBox::title { subcontrol-origin: margin; left: \
- 10px; padding: 0 3px 0 3px; } \
- ")
-
+ self.trbox.setLayout(self.trgrid)
return self.trbox
+
+ def set_theme(self, is_dark_theme):
+ """Update the theme and re-apply styling."""
+ self.is_dark_theme = is_dark_theme
+ self.apply_theme_styling()
diff --git a/src/maker/createkicad.py b/src/maker/createkicad.py
index ccc197198..3f30e8359 100644
--- a/src/maker/createkicad.py
+++ b/src/maker/createkicad.py
@@ -189,14 +189,9 @@ def removeOldLibrary(self):
def createSym(self):
'''
creating the symbol
- (pins snapped to KiCad-6 grid)
'''
- self.grid = 0.635
- self.dist_port = 4 * self.grid # Distance between two ports # 100 mil (= 2.54 mm)
- self.inc_size = self.dist_port # Increment size of a block (mil)
- def snap(val):
- snapped = round(float(val) / self.grid) * self.grid
- return f"{snapped:.3f}"
+ self.dist_port = 2.54 # Distance between two ports (mil)
+ self.inc_size = 2.54 # Increment size of a block (mil)
cwd = os.getcwd()
os.chdir(self.lib_loc)
print("Changing directory to ", self.lib_loc)
@@ -255,7 +250,7 @@ def snap(val):
draw_pos = \
[w.replace('comp_name', f"{self.modelname}_0_1") for w in draw_pos]
- draw_pos[8] = snap(float(draw_pos[8]) + # previously it is (-)
+ draw_pos[8] = str(float(draw_pos[8]) + # previously it is (-)
float(self.findBlockSize() * self.inc_size))
draw_pos_rec = draw_pos[8]
@@ -270,8 +265,6 @@ def snap(val):
input_port = input_port.split()
output_port = self.template["output_port"]
output_port = output_port.split()
- input_port[3] = snap(float(input_port[3]))
- output_port[3] = snap(float(output_port[3]))
inputs = self.portInfo[0: self.input_length]
outputs = self.portInfo[self.input_length:]
inputName = []
@@ -305,7 +298,7 @@ def snap(val):
input_port[9] = f"\"{inputName[i]}\""
input_port[13] = f"\"{str(i + 1)}\""
input_port[4] = \
- snap(float(input_port[4]) - float(self.dist_port))
+ str(float(input_port[4]) - float(self.dist_port))
input_list = ' '.join(input_port)
port_list.append(input_list)
j = j + 1
@@ -314,7 +307,7 @@ def snap(val):
output_port[9] = f"\"{outputName[i - inputs]}\""
output_port[13] = f"\"{str(i + 1)}\""
output_port[4] = \
- snap(float(output_port[4]) - float(self.dist_port))
+ str(float(output_port[4]) - float(self.dist_port))
output_list = ' '.join(output_port)
port_list.append(output_list)
diff --git a/src/maker/makerchip.py b/src/maker/makerchip.py
index 152c6cbb7..0127e2aa6 100755
--- a/src/maker/makerchip.py
+++ b/src/maker/makerchip.py
@@ -41,13 +41,8 @@ class makerchip(QtWidgets.QWidget):
# initialising the variables
def __init__(self, parent=None):
QtWidgets.QWidget.__init__(self)
-
- # filecount=int(open("a.txt",'r').read())
- print(filecount)
- # self.splitter.setOrientation(QtCore.Qt.Vertical)
- print("==================================")
- print("Makerchip and Verilog to Ngspice Converter")
- print("==================================")
+ self.maker_widget = None
+ self.ngveri_widget = None
self.createMainWindow()
# Creating the main Window(Main tab)
@@ -65,31 +60,57 @@ def createMainWindow(self):
# Creating the maker and ngveri widgets
def createWidget(self):
- global obj_Maker
global filecount
self.convertWindow = QtWidgets.QWidget()
+ # Get current theme from parent Application if possible
+ is_dark_theme = False
+ parent = self.parent()
+ if parent and hasattr(parent, 'is_dark_theme'):
+ is_dark_theme = parent.is_dark_theme
+
self.MakerTab = QtWidgets.QScrollArea()
- obj_Maker = Maker.Maker(filecount)
- self.MakerTab.setWidget(obj_Maker)
+ self.maker_widget = Maker.Maker(filecount, is_dark_theme=is_dark_theme)
+ self.MakerTab.setWidget(self.maker_widget)
self.MakerTab.setWidgetResizable(True)
- global obj_NgVeri
self.NgVeriTab = QtWidgets.QScrollArea()
- obj_NgVeri = NgVeri.NgVeri(filecount)
- self.NgVeriTab.setWidget(obj_NgVeri)
+ self.ngveri_widget = NgVeri.NgVeri(filecount, is_dark_theme=is_dark_theme)
+ self.NgVeriTab.setWidget(self.ngveri_widget)
self.NgVeriTab.setWidgetResizable(True)
+
self.tabWidget = QtWidgets.QTabWidget()
self.tabWidget.addTab(self.MakerTab, "Makerchip")
self.tabWidget.addTab(self.NgVeriTab, "NgVeri")
- # The object refresh gets destroyed when Ngspice\
- # to verilog converter is called
- # so calling refresh_change to start toggling of refresh again
- self.tabWidget.currentChanged.connect(obj_Maker.refresh_change)
+
+ # Re-apply theme on tab change
+ self.tabWidget.currentChanged.connect(self._on_tab_changed)
+
self.mainLayout = QtWidgets.QVBoxLayout()
+ self.mainLayout.setContentsMargins(15, 15, 15, 15) # Add margins
+ self.mainLayout.setSpacing(15) # Add spacing
self.mainLayout.addWidget(self.tabWidget)
+
self.convertWindow.setLayout(self.mainLayout)
self.convertWindow.show()
- # incrementing filecount for every new window
+
filecount = filecount + 1
return self.convertWindow
+
+ def set_theme(self, is_dark_theme):
+ """Update the theme for both Maker and NgVeri widgets."""
+ if self.maker_widget:
+ self.maker_widget.set_theme(is_dark_theme)
+ if self.ngveri_widget:
+ self.ngveri_widget.set_theme(is_dark_theme)
+
+ def _on_tab_changed(self, index):
+ # Ensure the correct theme is always applied to the active tab
+ is_dark_theme = False
+ parent = self.parent()
+ if parent and hasattr(parent, 'is_dark_theme'):
+ is_dark_theme = parent.is_dark_theme
+ if index == 0 and self.maker_widget:
+ self.maker_widget.set_theme(is_dark_theme)
+ elif index == 1 and self.ngveri_widget:
+ self.ngveri_widget.set_theme(is_dark_theme)
diff --git a/src/modelEditor/ModelEditor.py b/src/modelEditor/ModelEditor.py
index cddcc78f5..2b59c275b 100644
--- a/src/modelEditor/ModelEditor.py
+++ b/src/modelEditor/ModelEditor.py
@@ -1,3 +1,9 @@
+import sys
+import os
+sys.path.append(os.path.dirname(os.path.dirname(os.path.abspath(__file__))))
+
+from configuration.Appconfig import Appconfig
+
from PyQt5 import QtWidgets, QtCore
from PyQt5.Qt import QTableWidgetItem
import xml.etree.ElementTree as ET
@@ -28,9 +34,11 @@ class ModelEditorclass(QtWidgets.QWidget):
- Magnetic Core magnetic_click
'''
- def __init__(self):
+ def __init__(self, is_dark_theme=False):
QtWidgets.QWidget.__init__(self)
+ self.is_dark_theme = is_dark_theme
+
self.init_path = '../../'
if os.name == 'nt':
self.init_path = ''
@@ -112,8 +120,157 @@ def __init__(self):
self.grid.addWidget(self.igbt, 7, 1)
self.grid.addWidget(self.magnetic, 8, 1)
self.setLayout(self.grid)
+
+ # Apply initial theme styling
+ self.apply_theme_styling()
+
self.show()
+ def apply_theme_styling(self):
+ """Apply theme styling to the model editor widgets."""
+ self.setObjectName("model_editor")
+ if self.is_dark_theme:
+ self.setStyleSheet("""
+ QWidget { background: transparent; }
+ QPushButton { background: qlineargradient(x1:0, y1:0, x2:0, y2:1, stop:0 #40c4ff, stop:1 #1976d2); color: #181b24; border: 1px solid #40c4ff; min-height: 35px; min-width: 120px; padding: 8px 15px; border-radius: 10px; font-weight: 700; font-size: 12px; }
+ QPushButton:hover { background: #1976d2; color: #fff; border: 1.5px solid #1976d2; }
+ QPushButton:pressed { background: #23273a; color: #40c4ff; border: 1.5px solid #40c4ff; }
+ QPushButton:disabled { background: #23273a; color: #888; border: 1px solid #23273a; }
+ QRadioButton { color: #e8eaed; font-weight: 600; font-size: 13px; }
+ QRadioButton::indicator { width: 16px; height: 16px; border: 2px solid #40c4ff; border-radius: 8px; background: #23273a; }
+ QRadioButton::indicator:checked { background: #40c4ff; border: 2px solid #40c4ff; }
+ QComboBox { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; padding: 5px 10px; min-height: 30px; font-size: 12px; }
+ QComboBox:hover { border: 1.5px solid #1976d2; }
+ QComboBox::drop-down { border: none; width: 20px; }
+ QComboBox::down-arrow { width: 12px; height: 12px; }
+ QTableWidget { background: #23273a; color: #e8eaed; border: 1px solid #40c4ff; border-radius: 8px; gridline-color: #40c4ff; font-size: 12px; }
+ QTableWidget::item { padding: 8px; border-bottom: 1px solid #181b24; }
+ QTableWidget::item:selected { background: #40c4ff; color: #181b24; }
+ QHeaderView::section { background: #181b24; color: #40c4ff; border: 1px solid #40c4ff; padding: 8px; font-weight: 700; }
+ QLabel { color: #e8eaed; }
+ """)
+ else:
+ self.setStyleSheet("""
+ QGroupBox {
+ border-radius: 14px;
+ border: 2px solid #1976d2;
+ margin-top: 1em;
+ padding: 15px;
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 #ffffff, stop:1 #f8f9fa);
+ color: #2c3e50;
+ }
+ QGroupBox::title {
+ subcontrol-origin: margin;
+ left: 15px;
+ padding: 0 5px;
+ color: #1976d2;
+ font-weight: 600;
+ font-size: 14px;
+ }
+ QPushButton {
+ background: #ffffff;
+ color: #000000;
+ border: 1px solid #cccccc;
+ min-height: 25px;
+ min-width: 80px;
+ padding: 5px 10px;
+ border-radius: 6px;
+ font-weight: 600;
+ font-size: 12px;
+ }
+ QPushButton:hover {
+ background: #1976d2;
+ color: #ffffff;
+ border: 1px solid #1976d2;
+ }
+ QPushButton:pressed {
+ background: #1565c0;
+ color: #ffffff;
+ border: 1px solid #1565c0;
+ }
+ QPushButton:disabled {
+ background: #f5f5f5;
+ color: #999999;
+ border: 1px solid #e0e0e0;
+ }
+ QRadioButton {
+ color: #2c3e50;
+ font-weight: 200;
+ font-size: 13px;
+ spacing: 10px;
+ }
+
+ QRadioButton::indicator {
+ width: 16px;
+ height: 16px;
+ # border: 2px solid #1976d2;
+ border-radius: 8px;
+ background-color: #ffffff;
+ margin-right: 6px;
+ }
+
+ QRadioButton::indicator:checked {
+ background-color: #ffffff;
+ # border: 2px solid #1976d2;
+ }
+
+ QRadioButton::indicator:checked:after {
+ content: "";
+ background-color: #1976d2;
+ border-radius: 4px;
+ width: 8px;
+ height: 8px;
+ margin: 4px;
+ display: block;
+ }
+ QComboBox {
+ background: #ffffff;
+ color: #2c3e50;
+ border: 1px solid #1976d2;
+ border-radius: 8px;
+ padding: 5px 10px;
+ min-height: 30px;
+ font-size: 12px;
+ }
+ QComboBox:hover {
+ border: 1.5px solid #1565c0;
+ }
+ QComboBox::drop-down {
+ border: none;
+ width: 20px;
+ }
+ QComboBox::down-arrow {
+ width: 12px;
+ height: 12px;
+ }
+ QTableWidget {
+ background: #ffffff;
+ color: #2c3e50;
+ border: 1px solid #1976d2;
+ border-radius: 8px;
+ gridline-color: #1976d2;
+ font-size: 12px;
+ }
+ QTableWidget::item {
+ padding: 8px;
+ border-bottom: 1px solid #f8f9fa;
+ }
+ QTableWidget::item:selected {
+ background: #1976d2;
+ color: #ffffff;
+ }
+ QHeaderView::section {
+ background: #f8f9fa;
+ color: #1976d2;
+ border: 1px solid #1976d2;
+ padding: 8px;
+ font-weight: 700;
+ }
+ QLabel {
+ color: #2c3e50;
+}
+""")
def opennew(self):
'''
- To create New Model file
@@ -869,3 +1026,8 @@ def converttoxml(self):
os.chdir(defaultcwd)
libopen.close()
libopen1.close()
+
+ def set_theme(self, is_dark_theme):
+ """Update the theme and re-apply styling."""
+ self.is_dark_theme = is_dark_theme
+ self.apply_theme_styling()
diff --git a/src/modelEditor/ModelicaMapping.json b/src/modelEditor/ModelicaMapping.json
new file mode 100644
index 000000000..1cd3982e1
--- /dev/null
+++ b/src/modelEditor/ModelicaMapping.json
@@ -0,0 +1 @@
+{"Modelica": {"Basic": {}, "Electrical": {}, "Analog": {}}}
diff --git a/src/ngspiceSimulation/NgspiceWidget.py b/src/ngspiceSimulation/NgspiceWidget.py
index 6d8a9d742..cec54b7ad 100644
--- a/src/ngspiceSimulation/NgspiceWidget.py
+++ b/src/ngspiceSimulation/NgspiceWidget.py
@@ -2,7 +2,7 @@
from PyQt5 import QtWidgets, QtCore
from configuration.Appconfig import Appconfig
from frontEnd import TerminalUi
-from configparser import ConfigParser
+
# This Class creates NgSpice Window
class NgspiceWidget(QtWidgets.QWidget):
@@ -34,6 +34,17 @@ def __init__(self, netlist, simEndSignal, plotFlag):
self.layout = QtWidgets.QVBoxLayout(self)
self.layout.addWidget(self.terminalUi)
+ # --- Ensure the correct theme is applied immediately ---
+ app_parent = self.parent()
+ is_dark_theme = False
+ while app_parent is not None:
+ if hasattr(app_parent, 'is_dark_theme'):
+ is_dark_theme = app_parent.is_dark_theme
+ break
+ app_parent = app_parent.parent() if hasattr(app_parent, 'parent') else None
+ if hasattr(self, 'terminalUi') and hasattr(self.terminalUi, 'set_theme'):
+ self.terminalUi.set_theme(is_dark_theme)
+
# Receiving the plotFlag
self.plotFlag = plotFlag
print("Value of plotFlag: ", self.plotFlag)
@@ -188,30 +199,30 @@ def finishSimulation(self, exitCode, exitStatus,
def plotFlagFunc(self,projPath,command):
if self.plotFlag == True:
+ print("reached here too")
if os.name == 'nt':
parser_nghdl = ConfigParser()
- config_path = os.path.join('library', 'config', '.nghdl', 'config.ini')
- parser_nghdl.read(config_path)
+ parser_nghdl.read(
+ os.path.join('library', 'config', '.nghdl', 'config.ini')
+ )
+
msys_home = parser_nghdl.get('COMPILER', 'MSYS_HOME')
+
tempdir = os.getcwd()
projPath = self.obj_appconfig.current_project["ProjectName"]
os.chdir(projPath)
-
- self.command = (
- 'cmd /c "start /min ' +
- msys_home + '/usr/bin/mintty.exe ngspice -p ' + command + '"'
- )
-
- # Create a new QProcess for mintty
- self.minttyProcess = QtCore.QProcess(self)
- self.minttyProcess.start(self.command)
+ self.command = 'cmd /c ' + '"start /min ' + \
+ msys_home + "/usr/bin/mintty.exe ngspice -p " + command + '"'
+ self.process.start(self.command)
os.chdir(tempdir)
else:
+ print("reached .. 4")
self.commandi = "cd " + projPath + \
";ngspice -r " + command.replace(".cir.out", ".raw") + \
" " + command
self.xtermArgs = ['-hold', '-e', self.commandi]
+ print("xTerm")
self.xtermProcess = QtCore.QProcess(self)
self.xtermProcess.start('xterm', self.xtermArgs)
diff --git a/src/ngspiceSimulation/__init__.py b/src/ngspiceSimulation/__init__.py
index e69de29bb..a9bb73b20 100644
--- a/src/ngspiceSimulation/__init__.py
+++ b/src/ngspiceSimulation/__init__.py
@@ -0,0 +1,4 @@
+from .pythonPlotting import plotWindow
+from .NgspiceWidget import NgspiceWidget
+
+__all__ = ['plotWindow', 'NgspiceWidget']
diff --git a/src/ngspiceSimulation/pythonPlotting.py b/src/ngspiceSimulation/pythonPlotting.py
index 615ad02b5..7571319a2 100644
--- a/src/ngspiceSimulation/pythonPlotting.py
+++ b/src/ngspiceSimulation/pythonPlotting.py
@@ -10,7 +10,325 @@
from matplotlib.figure import Figure
from configuration.Appconfig import Appconfig
import numpy as np
-
+import re
+from cycler import cycler
+
+# Dark theme colors - Modern GitHub Dark inspired theme
+DARK_BLUE = "#0d1117" # Main background
+LIGHTER_BLUE = "#161b22" # Secondary background
+ACCENT_BLUE = "#1f6feb" # Primary accent
+ACCENT_HOVER = "#388bfd" # Hover state
+TEXT_COLOR = "#f0f6fc" # Main text
+SECONDARY_TEXT = "#8b949e" # Secondary text
+BORDER_COLOR = "#30363d" # Borders
+GRADIENT_START = "#1f2937" # Background gradient start
+GRADIENT_END = "#111827" # Background gradient end
+
+# Light theme colors - Modern GitHub Light inspired theme
+LIGHT_BG = "#ffffff" # Main background
+LIGHT_SECONDARY = "#f6f8fa" # Secondary background
+LIGHT_ACCENT = "#0969da" # Primary accent
+LIGHT_ACCENT_HOVER = "#1a7f37" # Hover state
+LIGHT_TEXT = "#24292f" # Main text
+LIGHT_SECONDARY_TEXT = "#57606a" # Secondary text
+LIGHT_BORDER = "#d0d7de" # Borders
+LIGHT_GRADIENT_START = "#f6f8fa" # Background gradient start
+LIGHT_GRADIENT_END = "#ffffff" # Background gradient end
+
+# Toolbar icon size
+TOOLBAR_ICON_SIZE = 24 # Size for toolbar icons in pixels
+
+# Define the stylesheets
+DARK_STYLESHEET = f"""
+ /* Main window and widget styling */
+ QMainWindow {{
+ background: {DARK_BLUE};
+ }}
+
+ QWidget {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {GRADIENT_START}, stop:1 {GRADIENT_END});
+ color: {TEXT_COLOR};
+ font-size: 14px;
+ }}
+ QMainWindow, QWidget {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {GRADIENT_START}, stop:1 {GRADIENT_END});
+ color: {TEXT_COLOR};
+ font-size: 14px;
+ }}
+
+ QPushButton {{
+ background-color: {ACCENT_BLUE};
+ color: {TEXT_COLOR};
+ border: 2px solid {ACCENT_HOVER};
+ padding: 6px 12px;
+ border-radius: 4px;
+ min-width: 80px;
+ font-size: 13px;
+ font-weight: bold;
+ margin: 1px;
+ }}
+ QPushButton:hover {{
+ background-color: {ACCENT_HOVER};
+ border-color: {TEXT_COLOR};
+ color: {TEXT_COLOR};
+ }}
+ QPushButton:pressed {{
+ background-color: {GRADIENT_START};
+ border-color: {ACCENT_HOVER};
+ color: {TEXT_COLOR};
+ }}
+
+ QLabel {{
+ color: {TEXT_COLOR};
+ font-size: 14px;
+ font-weight: bold;
+ padding: 4px;
+ }}
+
+ QLineEdit {{
+ background-color: {LIGHTER_BLUE};
+ color: {TEXT_COLOR};
+ border: 2px solid {BORDER_COLOR};
+ padding: 10px;
+ border-radius: 6px;
+ font-size: 14px;
+ }}
+ QLineEdit:focus {{
+ border-color: {ACCENT_BLUE};
+ }}
+
+ QCheckBox {{
+ color: {TEXT_COLOR};
+ spacing: 8px;
+ font-size: 14px;
+ padding: 4px;
+ }}
+ QCheckBox::indicator {{
+ width: 20px;
+ height: 20px;
+ border-radius: 4px;
+ }}
+ QCheckBox::indicator:unchecked {{
+ border: 2px solid {BORDER_COLOR};
+ background-color: {LIGHTER_BLUE};
+ }}
+ QCheckBox::indicator:checked {{
+ border: 2px solid {ACCENT_HOVER};
+ background-color: {ACCENT_BLUE};
+ }}
+ QCheckBox::indicator:hover {{
+ border-color: {TEXT_COLOR};
+ }}
+
+ QScrollArea {{
+ border: 2px solid {BORDER_COLOR};
+ border-radius: 8px;
+ background-color: transparent;
+ }}
+
+ QToolBar {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {GRADIENT_START}, stop:1 {GRADIENT_END});
+ border-bottom: 2px solid {ACCENT_BLUE};
+ padding: 8px;
+ spacing: 8px;
+ min-height: 48px;
+ }}
+
+ QToolButton {{
+ background-color: {LIGHTER_BLUE};
+ border: 2px solid {BORDER_COLOR};
+ border-radius: 6px;
+ padding: 8px;
+ margin: 2px;
+ min-width: 36px;
+ min-height: 36px;
+ font-size: 14px;
+ color: {TEXT_COLOR};
+ }}
+ QToolButton:hover {{
+ background-color: {ACCENT_BLUE};
+ border-color: {ACCENT_HOVER};
+ color: {TEXT_COLOR};
+ }}
+ QToolButton:pressed {{
+ background-color: {GRADIENT_START};
+ border-color: {TEXT_COLOR};
+ color: {TEXT_COLOR};
+ }}
+"""
+
+# Light theme stylesheet
+LIGHT_STYLESHEET = f"""
+ /* Main window and widget styling */
+ QMainWindow {{
+ background: {LIGHT_BG};
+ }}
+
+ QWidget {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {LIGHT_GRADIENT_START}, stop:1 {LIGHT_GRADIENT_END});
+ color: {LIGHT_TEXT};
+ font-size: 14px;
+ }}
+ QMainWindow, QWidget {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {LIGHT_GRADIENT_START}, stop:1 {LIGHT_GRADIENT_END});
+ color: {LIGHT_TEXT};
+ font-size: 14px;
+ }}
+
+ QPushButton {{
+ background-color: {LIGHT_ACCENT};
+ color: #24292f;
+ border: 2px solid {LIGHT_ACCENT_HOVER};
+ padding: 6px 12px;
+ border-radius: 4px;
+ min-width: 80px;
+ font-size: 13px;
+ font-weight: bold;
+ margin: 1px;
+ }}
+ QPushButton:hover {{
+ background-color: {LIGHT_ACCENT_HOVER};
+ color: #24292f;
+ border-color: {LIGHT_TEXT};
+ }}
+ QPushButton:pressed {{
+ background-color: {LIGHT_GRADIENT_START};
+ color: #24292f;
+ border-color: {LIGHT_ACCENT_HOVER};
+ }}
+
+ QLabel {{
+ color: {LIGHT_TEXT};
+ font-size: 14px;
+ font-weight: bold;
+ padding: 4px;
+ }}
+
+ QLineEdit {{
+ background-color: {LIGHT_SECONDARY};
+ color: {LIGHT_TEXT};
+ border: 2px solid {LIGHT_BORDER};
+ padding: 10px;
+ border-radius: 6px;
+ font-size: 14px;
+ }}
+ QLineEdit:focus {{
+ border-color: {LIGHT_ACCENT};
+ }}
+
+ QCheckBox {{
+ color: {LIGHT_TEXT};
+ spacing: 8px;
+ font-size: 14px;
+ padding: 4px;
+ }}
+ QCheckBox::indicator {{
+ width: 20px;
+ height: 20px;
+ border-radius: 4px;
+ }}
+ QCheckBox::indicator:unchecked {{
+ border: 2px solid {LIGHT_BORDER};
+ background-color: {LIGHT_SECONDARY};
+ }}
+ QCheckBox::indicator:checked {{
+ border: 2px solid {LIGHT_ACCENT_HOVER};
+ background-color: {LIGHT_ACCENT};
+ }}
+ QCheckBox::indicator:hover {{
+ border-color: {LIGHT_TEXT};
+ }}
+
+ QScrollArea {{
+ border: 2px solid {LIGHT_BORDER};
+ border-radius: 8px;
+ background-color: transparent;
+ }}
+
+ QToolBar {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {LIGHT_GRADIENT_START}, stop:1 {LIGHT_GRADIENT_END});
+ border-bottom: 2px solid {LIGHT_ACCENT};
+ padding: 8px;
+ spacing: 8px;
+ min-height: 48px;
+ }}
+
+ QToolButton {{
+ background-color: {LIGHT_SECONDARY};
+ border: 2px solid {LIGHT_BORDER};
+ border-radius: 6px;
+ padding: 8px;
+ margin: 2px;
+ min-width: 36px;
+ min-height: 36px;
+ font-size: 14px;
+ color: {LIGHT_TEXT};
+ }}
+ QToolButton:hover {{
+ background-color: {LIGHT_ACCENT};
+ border-color: {LIGHT_ACCENT_HOVER};
+ color: {LIGHT_TEXT};
+ }}
+ QToolButton:pressed {{
+ background-color: {LIGHT_GRADIENT_START};
+ border-color: {LIGHT_TEXT};
+ }}
+"""
+
+# Multimeter widget styles
+DARK_MULTIMETER_STYLE = f"""
+ QWidget {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {GRADIENT_START}, stop:1 {GRADIENT_END});
+ color: {TEXT_COLOR};
+ border: 2px solid {BORDER_COLOR};
+ border-radius: 8px;
+ padding: 10px;
+ }}
+ QLabel {{
+ color: {TEXT_COLOR};
+ padding: 8px;
+ font-size: 13px;
+ font-weight: bold;
+ background: transparent;
+ border: none;
+ }}
+ QLabel[class="value"] {{
+ color: {ACCENT_BLUE};
+ font-size: 16px;
+ font-weight: bold;
+ }}
+"""
+
+LIGHT_MULTIMETER_STYLE = f"""
+ QWidget {{
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 {LIGHT_GRADIENT_START}, stop:1 {LIGHT_GRADIENT_END});
+ color: {LIGHT_TEXT};
+ border: 2px solid {LIGHT_BORDER};
+ border-radius: 8px;
+ padding: 10px;
+ }}
+ QLabel {{
+ color: {LIGHT_TEXT};
+ padding: 8px;
+ font-size: 13px;
+ font-weight: bold;
+ background: transparent;
+ border: none;
+ }}
+ QLabel[class="value"] {{
+ color: {LIGHT_ACCENT};
+ font-size: 16px;
+ font-weight: bold;
+ }}
+"""
# This class creates Python Plotting window
class plotWindow(QtWidgets.QMainWindow):
@@ -18,8 +336,35 @@ class plotWindow(QtWidgets.QMainWindow):
This class defines python plotting window, its features, buttons,
colors, AC and DC analysis, plotting etc.
"""
-
- def __init__(self, fpath, projectName):
+
+ # Class variable to store the single instance
+ instance = None
+
+ @classmethod
+ def add_output(cls, fpath, projectName, is_dark_theme=True):
+ """Static method to manage plot window instances.
+
+ Args:
+ fpath (str): Path to the project directory
+ projectName (str): Name of the project
+ is_dark_theme (bool): Whether to use dark theme (default: True)
+ """
+ if cls.instance is None:
+ cls.instance = cls(fpath, projectName, is_dark_theme)
+ else:
+ # Update existing instance with new data
+ cls.instance.fpath = fpath
+ cls.instance.projectName = projectName
+ cls.instance.is_dark_theme = is_dark_theme
+ cls.instance.obj_dataext = DataExtraction()
+ cls.instance.plotType = cls.instance.obj_dataext.openFile(fpath)
+ cls.instance.obj_dataext.computeAxes()
+ cls.instance.a = cls.instance.obj_dataext.numVals()
+ cls.instance.createMainFrame()
+
+ return cls.instance
+
+ def __init__(self, fpath, projectName, is_dark_theme=True):
"""This create constructor for plotWindow class."""
QtWidgets.QMainWindow.__init__(self)
self.fpath = fpath
@@ -34,42 +379,275 @@ def __init__(self, fpath, projectName):
self.combo = []
self.combo1 = []
self.combo1_rev = []
- # Creating Frame
+ self.is_dark_theme = is_dark_theme
+ # Only apply stylesheet for dark mode
+ if self.is_dark_theme:
+ self.setStyleSheet(DARK_STYLESHEET)
+ # Set global tooltip style for dark mode
+ app = QtWidgets.QApplication.instance()
+ if app:
+ app.setStyleSheet('''
+ QToolTip {
+ background-color: #23272e;
+ color: #fff;
+ border: 1px solid #388bfd;
+ border-radius: 6px;
+ font-size: 13px;
+ padding: 6px;
+ }
+ ''')
+ else:
+ self.setStyleSheet(LIGHT_STYLESHEET) # Use light stylesheet for light mode
+ # Reset tooltip style for light mode
+ app = QtWidgets.QApplication.instance()
+ if app:
+ app.setStyleSheet('')
self.createMainFrame()
+ def toggle_theme(self):
+ """Toggle between light and dark themes."""
+ self.is_dark_theme = not self.is_dark_theme
+ self.setStyleSheet(DARK_STYLESHEET if self.is_dark_theme else LIGHT_STYLESHEET)
+
+ # Update tooltip styling based on theme
+ app = QtWidgets.QApplication.instance()
+ if app:
+ if self.is_dark_theme:
+ app.setStyleSheet('''
+ QToolTip {
+ background-color: #23272e;
+ color: #fff;
+ border: 1px solid #388bfd;
+ border-radius: 6px;
+ font-size: 13px;
+ padding: 6px;
+ }
+ ''')
+ else:
+ app.setStyleSheet('')
+
+ self.update_plot_theme()
+
+ def update_plot_theme(self):
+ """Update plot colors based on current theme."""
+ if self.is_dark_theme:
+ # Dark theme colors
+ bg_color = DARK_BLUE
+ text_color = TEXT_COLOR # Use white for all text
+ accent_color = ACCENT_BLUE
+ grid_color = BORDER_COLOR
+ function_color = TEXT_COLOR # White for dark theme
+ # Set a bright color cycle for plot lines in dark mode
+ self.axes.set_prop_cycle(cycler('color', ['#00eaff', '#ff6b6b', '#ffe156', '#6bffb4', '#a55eea', '#fd79a8', '#ffb347', '#f9ca24', '#4ecdc4', '#45b7d1']))
+ else:
+ # Light theme colors
+ bg_color = LIGHT_BG
+ text_color = LIGHT_TEXT
+ accent_color = LIGHT_ACCENT
+ grid_color = LIGHT_BORDER
+ function_color = LIGHT_TEXT # Black for light theme
+ self.axes.set_prop_cycle(cycler('color', ['#00eaff', '#ff6b6b', '#ffe156', '#6bffb4', '#a55eea', '#fd79a8', '#ffb347', '#f9ca24', '#4ecdc4', '#45b7d1']))
+
+ # Update figure and axes colors
+ self.fig.patch.set_facecolor(bg_color)
+ self.axes.set_facecolor(bg_color)
+
+ # Update text colors
+ self.axes.tick_params(colors=text_color, labelsize=12)
+ self.axes.xaxis.label.set_color(text_color)
+ self.axes.yaxis.label.set_color(text_color)
+ self.axes.title.set_color(text_color)
+
+ # Update spines
+ for spine in self.axes.spines.values():
+ spine.set_color(accent_color)
+ spine.set_linewidth(2)
+
+ # Update grid
+ self.axes.grid(True, color=grid_color, alpha=0.3)
+
+ # Update function text colors
+ for text in self.axes.texts:
+ text.set_color(function_color)
+
+ # Update annotation colors
+ for annotation in self.axes.annotations if hasattr(self.axes, 'annotations') else []:
+ annotation.set_color(function_color)
+ # Update all children that are Text (for annotations, etc.)
+ for child in self.axes.get_children():
+ if hasattr(child, 'set_color') and hasattr(child, 'get_text') and child.get_text() != '':
+ child.set_color(function_color)
+
+ # Update legend colors if it exists
+ if self.axes.get_legend():
+ legend = self.axes.get_legend()
+ legend.get_frame().set_facecolor(bg_color)
+ legend.get_frame().set_edgecolor(accent_color)
+ for text in legend.get_texts():
+ text.set_color(text_color)
+
+ # Redraw the canvas
+ self.canvas.draw()
+
+ # Update coordinates label color for dark mode
+ if hasattr(self, 'coord_label') and self.coord_label:
+ if self.is_dark_theme:
+ self.coord_label.setStyleSheet('font-size: 12px; padding-left: 8px; color: #f0f6fc;')
+ else:
+ self.coord_label.setStyleSheet('font-size: 12px; padding-left: 8px; color: #24292f;')
+
+ # Update multimeter themes if they exist
+ for widget in self.findChildren(MultimeterWidgetClass):
+ widget.toggle_theme()
+
+ # Update right panel label and checkbox colors for theme
+ if self.is_dark_theme:
+ self.analysisType.setStyleSheet('color: #f0f6fc;')
+ self.listNode.setStyleSheet('color: #f0f6fc;')
+ self.listBranch.setStyleSheet('color: #f0f6fc;')
+ self.funcLabel.setStyleSheet('color: #f0f6fc;')
+ self.funcName.setStyleSheet('color: #f0f6fc;')
+ self.funcExample.setStyleSheet('color: #f0f6fc;')
+ for cb in self.chkbox:
+ cb.setStyleSheet('color: #f0f6fc;')
+ else:
+ self.analysisType.setStyleSheet('color: #24292f;')
+ self.listNode.setStyleSheet('color: #24292f;')
+ self.listBranch.setStyleSheet('color: #24292f;')
+ self.funcLabel.setStyleSheet('color: #24292f;')
+ self.funcName.setStyleSheet('color: #24292f;')
+ self.funcExample.setStyleSheet('color: #24292f;')
+ for cb in self.chkbox:
+ cb.setStyleSheet('color: #24292f;')
+
def createMainFrame(self):
self.mainFrame = QtWidgets.QWidget()
self.dpi = 100
- self.fig = Figure((7.0, 7.0), dpi=self.dpi)
- # Creating Canvas which will figure
+ if self.is_dark_theme:
+ self.fig = Figure((7.0, 7.0), dpi=self.dpi, facecolor="#000000") # Black canvas
+ else:
+ self.fig = Figure((7.0, 7.0), dpi=self.dpi) # Default white bg
self.canvas = FigureCanvas(self.fig)
self.canvas.setParent(self.mainFrame)
self.axes = self.fig.add_subplot(111)
+ # Set axes and tick colors for dark mode
+ if self.is_dark_theme:
+ self.axes.set_facecolor("#000000")
+ self.axes.tick_params(colors="#f0f6fc", labelsize=12)
+ self.axes.xaxis.label.set_color("#f0f6fc")
+ self.axes.yaxis.label.set_color("#f0f6fc")
+ self.axes.title.set_color("#f0f6fc")
+ for spine in self.axes.spines.values():
+ spine.set_color("#1f6feb")
+ spine.set_linewidth(2)
self.navToolBar = NavigationToolbar(self.canvas, self.mainFrame)
-
- # LeftVbox hold navigation tool bar and canvas
self.left_vbox = QtWidgets.QVBoxLayout()
- self.left_vbox.addWidget(self.navToolBar)
+ # Custom toolbar for both dark and light mode
+ self.navToolBar.hide()
+ custom_toolbar = QtWidgets.QWidget()
+ custom_toolbar_layout = QtWidgets.QHBoxLayout()
+ custom_toolbar_layout.setContentsMargins(0, 0, 0, 0)
+ custom_toolbar_layout.setSpacing(0) # Minimum horizontal spacing
+ for action in self.navToolBar.actions():
+ if action.isSeparator() or action.icon().isNull():
+ continue # Skip separators and actions without icons
+ btn = QtWidgets.QToolButton()
+ btn.setDefaultAction(action)
+ btn.setIcon(action.icon())
+ btn.setToolTip(action.toolTip())
+ # Modern styling for both modes
+ if self.is_dark_theme:
+ btn.setStyleSheet('''
+ QToolButton {
+ background-color: #23272e;
+ border: 2px solid #30363d;
+ border-radius: 8px;
+ padding: 4px;
+ margin: 0px;
+ min-width: 28px;
+ min-height: 28px;
+ font-size: 13px;
+ color: #f0f6fc;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.15);
+ transition: background 0.2s, border 0.2s;
+ }
+ QToolButton:hover {
+ background-color: #1f6feb;
+ color: #f0f6fc;
+ border-color: #388bfd;
+ }
+ QToolButton:pressed {
+ background-color: #161b22;
+ color: #f0f6fc;
+ border-color: #f0f6fc;
+ }
+ QToolButton:checked {
+ background-color: #388bfd;
+ color: #f0f6fc;
+ border-color: #f0f6fc;
+ }
+ ''')
+ else:
+ btn.setStyleSheet('''
+ QToolButton {
+ background-color: #fff;
+ border: 2px solid #d0d7de;
+ border-radius: 8px;
+ padding: 4px;
+ margin: 0px;
+ min-width: 28px;
+ min-height: 28px;
+ font-size: 13px;
+ color: #24292f;
+ box-shadow: 0 2px 8px rgba(0,0,0,0.05);
+ transition: background 0.2s, border 0.2s;
+ }
+ QToolButton:hover {
+ background-color: #f6f8fa;
+ border-color: #0969da;
+ color: #24292f;
+ }
+ QToolButton:pressed {
+ background-color: #eaeef2;
+ border-color: #24292f;
+ }
+ ''')
+ vbox = QtWidgets.QVBoxLayout()
+ vbox.setAlignment(QtCore.Qt.AlignHCenter)
+ vbox.setContentsMargins(0, 0, 0, 0) # Remove margins between button+label
+ vbox.addWidget(btn, alignment=QtCore.Qt.AlignHCenter)
+ label = QtWidgets.QLabel()
+ label.setAlignment(QtCore.Qt.AlignHCenter)
+ tooltip_plain = re.sub('<[^<]+?>', '', action.toolTip())
+ label.setText(tooltip_plain)
+ label.setStyleSheet('font-size: 9px; color: gray; margin-top: 0px;')
+ vbox.addWidget(label)
+ custom_toolbar_layout.addLayout(vbox)
+ # Add a custom QLabel for coordinates display to the far right
+ self.coord_label = QtWidgets.QLabel()
+ self.coord_label.setVisible(True)
+ if self.is_dark_theme:
+ self.coord_label.setStyleSheet('font-size: 12px; padding-left: 8px; color: #f0f6fc;')
+ custom_toolbar_layout.addStretch(1)
+ custom_toolbar_layout.addWidget(self.coord_label, alignment=QtCore.Qt.AlignVCenter)
+ custom_toolbar.setLayout(custom_toolbar_layout)
+ self.left_vbox.addWidget(custom_toolbar)
self.left_vbox.addWidget(self.canvas)
-
- # right VBOX is main Layout which hold right grid(bottom part) and top
- # grid(top part)
+ # Explicitly connect mpl_connect to update coordinates in the custom label
+ self.canvas.mpl_connect('motion_notify_event', self.update_coordinates)
self.right_vbox = QtWidgets.QVBoxLayout()
self.right_grid = QtWidgets.QGridLayout()
self.top_grid = QtWidgets.QGridLayout()
-
- # Get DataExtraction Details
self.obj_dataext = DataExtraction()
self.plotType = self.obj_dataext.openFile(self.fpath)
-
self.obj_dataext.computeAxes()
self.a = self.obj_dataext.numVals()
-
self.chkbox = []
-
- # Generating list of colors :
- # ,(0.4,0.5,0.2),(0.1,0.4,0.9),(0.4,0.9,0.2),(0.9,0.4,0.9)]
- self.full_colors = ['r', 'b', 'g', 'y', 'c', 'm', 'k']
+ # Color palette
+ if self.is_dark_theme:
+ self.full_colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#a55eea', '#fd79a8', '#00d2d3']
+ else:
+ self.full_colors = ['#ff6b6b', '#4ecdc4', '#45b7d1', '#f9ca24', '#a55eea', '#fd79a8', '#00d2d3']
self.color = []
for i in range(0, self.a[0] - 1):
if i % 7 == 0:
@@ -86,10 +664,6 @@ def createMainFrame(self):
self.color.append(self.full_colors[5])
elif (i - 6) % 7 == 0:
self.color.append(self.full_colors[6])
-
- # Color generation ends here
-
- # Total number of voltage source
self.volts_length = self.a[1]
self.analysisType = QtWidgets.QLabel()
self.top_grid.addWidget(self.analysisType, 0, 0)
@@ -99,7 +673,10 @@ def createMainFrame(self):
self.top_grid.addWidget(self.listBranch, self.a[1] + 2, 0)
for i in range(0, self.a[1]): # a[0]-1
self.chkbox.append(QtWidgets.QCheckBox(self.obj_dataext.NBList[i]))
- self.chkbox[i].setStyleSheet('color')
+ if self.is_dark_theme:
+ self.chkbox[i].setStyleSheet('color: #f0f6fc;')
+ else:
+ self.chkbox[i].setStyleSheet('color: #24292f;')
self.chkbox[i].setToolTip('Check To Plot')
self.top_grid.addWidget(self.chkbox[i], i + 2, 0)
self.colorLab = QtWidgets.QLabel()
@@ -112,6 +689,10 @@ def createMainFrame(self):
for i in range(self.a[1], self.a[0] - 1): # a[0]-1
self.chkbox.append(QtWidgets.QCheckBox(self.obj_dataext.NBList[i]))
+ if self.is_dark_theme:
+ self.chkbox[i].setStyleSheet('color: #f0f6fc;')
+ else:
+ self.chkbox[i].setStyleSheet('color: #24292f;')
self.chkbox[i].setToolTip('Check To Plot')
self.top_grid.addWidget(self.chkbox[i], i + 3, 0)
self.colorLab = QtWidgets.QLabel()
@@ -140,6 +721,12 @@ def createMainFrame(self):
self.plotfuncbtn = QtWidgets.QPushButton("Plot Function")
self.plotfuncbtn.setToolTip('Press to Plot the function')
+ # Set button text color explicitly for dark/light mode
+ self.plotbtn.setStyleSheet(f'color: {"#f0f6fc" if self.is_dark_theme else "#24292f"};')
+ self.clear.setStyleSheet(f'color: {"#f0f6fc" if self.is_dark_theme else "#24292f"};')
+ self.multimeterbtn.setStyleSheet(f'color: {"#f0f6fc" if self.is_dark_theme else "#24292f"};')
+ self.plotfuncbtn.setStyleSheet(f'color: {"#f0f6fc" if self.is_dark_theme else "#24292f"};')
+
self.palette1.setColor(QtGui.QPalette.Foreground, QtCore.Qt.blue)
self.palette2.setColor(QtGui.QPalette.Foreground, QtCore.Qt.red)
self.funcName.setPalette(self.palette1)
@@ -158,37 +745,54 @@ def createMainFrame(self):
self.right_grid.addWidget(self.funcExample, 4, 1)
self.right_vbox.addLayout(self.right_grid)
+ # Set background colors for full window, right panel, and scroll area
+ bg_color = DARK_BLUE if self.is_dark_theme else LIGHT_BG
+ self.mainFrame.setStyleSheet(f"background-color: {bg_color};")
+ self.right_vbox.setContentsMargins(0, 0, 0, 0)
+ self.right_vbox.setSpacing(4)
+ self.right_grid.setContentsMargins(0, 0, 0, 0)
+ self.right_grid.setSpacing(4)
+ self.top_grid.setContentsMargins(0, 0, 0, 0)
+ self.top_grid.setSpacing(4)
+ # Set right panel background
+ right_panel_widget = QtWidgets.QWidget()
+ right_panel_widget.setLayout(self.right_vbox)
+ right_panel_widget.setStyleSheet(f"background-color: {bg_color};")
+ # Replace right_vbox in hbox with right_panel_widget
self.hbox = QtWidgets.QHBoxLayout()
- self.hbox.addLayout(self.left_vbox)
- self.hbox.addLayout(self.right_vbox)
-
+ self.hbox.addLayout(self.left_vbox, stretch=4)
+ self.hbox.addWidget(right_panel_widget, stretch=1)
self.widget = QtWidgets.QWidget()
- self.widget.setLayout(self.hbox) # finalvbox
+ self.widget.setLayout(self.hbox)
self.scrollArea = QtWidgets.QScrollArea()
self.scrollArea.setWidgetResizable(True)
self.scrollArea.setWidget(self.widget)
- '''
- Right side box containing checkbox for different inputs and
- options of plot, multimeter and plot function.
- '''
+ self.scrollArea.setStyleSheet(f"background-color: {bg_color};")
self.finalhbox = QtWidgets.QHBoxLayout()
self.finalhbox.addWidget(self.scrollArea)
- # Right side window frame showing list of nodes and branches.
self.mainFrame.setLayout(self.finalhbox)
self.showMaximized()
- self.listNode.setText("List of Nodes:")
+ self.listNode.setText(f"List of Nodes:
")
self.listBranch.setText(
- "List of Branches:")
- self.funcLabel.setText("Function:")
+ f"List of Branches:
")
+ self.funcLabel.setText(f"Function:
")
self.funcName.setText(
- "Standard functions\
-
Addition:
Subtraction:
\
- Multiplication:
Division:
Comparison:"
+ f"Standard functions
\
+ \
+ Addition:
\
+ Subtraction:
\
+ Multiplication:
\
+ Division:
\
+ Comparison:
"
)
self.funcExample.setText(
- "\n\nNode1 + Node2\nNode1 - Node2\nNode1 * Node2\nNode1 / Node2\
- \nNode1 vs Node2")
+ f"\
+ Node1 + Node2
\
+ Node1 - Node2
\
+ Node1 * Node2
\
+ Node1 / Node2
\
+ Node1 vs Node2
")
# Connecting to plot and clear function
self.clear.clicked.connect(self.pushedClear)
@@ -217,6 +821,8 @@ def createMainFrame(self):
def pushedClear(self):
self.text.clear()
self.axes.cla()
+ # Reapply theme after clearing
+ self.update_plot_theme()
self.canvas.draw()
def pushedPlotFunc(self):
@@ -296,11 +902,11 @@ def pushedPlotFunc(self):
label=str(2)) # _rev
if max(a) < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
- self.axes.set_xlabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage(V)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
+ self.axes.set_xlabel('Voltage(V)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current(I)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
+ self.axes.set_xlabel('Current(I)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
elif max(a) >= self.volts_length and min(a) < self.volts_length:
QtWidgets.QMessageBox.about(
@@ -337,12 +943,12 @@ def pushedPlotFunc(self):
c=self.color[0],
label=str(1))
- self.axes.set_xlabel('freq-->')
+ self.axes.set_xlabel('freq-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if max(a) < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage(V)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current(I)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
elif self.plotType2[0] == 1:
# self.setWindowTitle('Transient Analysis')
@@ -351,11 +957,11 @@ def pushedPlotFunc(self):
finalResult,
c=self.color[0],
label=str(1))
- self.axes.set_xlabel('time-->')
+ self.axes.set_xlabel('time-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if max(a) < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage(V)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current(I)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
# self.setWindowTitle('DC Analysis')
@@ -364,13 +970,15 @@ def pushedPlotFunc(self):
finalResult,
c=self.color[0],
label=str(1))
- self.axes.set_xlabel('I/P Voltage-->')
+ self.axes.set_xlabel('I/P Voltage-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if max(a) < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage(V)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current(I)-->', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
self.axes.grid(True)
+ # Reapply theme after plotting
+ self.update_plot_theme()
self.canvas.draw()
self.combo = []
self.combo1 = []
@@ -391,11 +999,11 @@ def onPush_decade(self):
c=self.color[j],
label=str(
j + 1))
- self.axes.set_xlabel('freq-->')
+ self.axes.set_xlabel('Frequency', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if j < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage (V)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current (A)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
self.axes.grid(True)
if boxCheck == 0:
@@ -403,7 +1011,9 @@ def onPush_decade(self):
self, "Warning!!", "Please select at least one Node OR Branch"
)
return
-
+
+ # Reapply theme after plotting
+ self.update_plot_theme()
self.canvas.draw()
def onPush_ac(self):
@@ -418,11 +1028,11 @@ def onPush_ac(self):
c=self.color[j],
label=str(
j + 1))
- self.axes.set_xlabel('freq-->')
+ self.axes.set_xlabel('Frequency', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if j < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage (V)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current (A)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
self.axes.grid(True)
if boxCheck == 0:
QtWidgets.QMessageBox.about(
@@ -430,6 +1040,8 @@ def onPush_ac(self):
)
return
+ # Reapply theme after plotting
+ self.update_plot_theme()
self.canvas.draw()
def onPush_trans(self):
@@ -444,17 +1056,20 @@ def onPush_trans(self):
c=self.color[j],
label=str(
j + 1))
- self.axes.set_xlabel('time-->')
+ self.axes.set_xlabel('Time', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if j < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage (V)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current (A)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
self.axes.grid(True)
if boxCheck == 0:
QtWidgets.QMessageBox.about(
self, "Warning!!", "Please select at least one Node OR Branch"
)
return
+
+ # Reapply theme after plotting
+ self.update_plot_theme()
self.canvas.draw()
def onPush_dc(self):
@@ -469,12 +1084,11 @@ def onPush_dc(self):
c=self.color[j],
label=str(
j + 1))
- self.axes.set_xlabel('Voltage Sweep(V)-->')
-
+ self.axes.set_xlabel('Voltage Sweep (V)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
if j < self.volts_length:
- self.axes.set_ylabel('Voltage(V)-->')
+ self.axes.set_ylabel('Voltage (V)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
else:
- self.axes.set_ylabel('Current(I)-->')
+ self.axes.set_ylabel('Current (A)', fontsize=14, fontweight='bold', color=ACCENT_HOVER)
self.axes.grid(True)
if boxCheck == 0:
QtWidgets.QMessageBox.about(
@@ -482,18 +1096,12 @@ def onPush_dc(self):
)
return
+ # Reapply theme after plotting
+ self.update_plot_theme()
self.canvas.draw()
- def colorName(self, letter):
- return {
- 'r': 'color:red',
- 'b': 'color:blue',
- 'g': 'color:green',
- 'y': 'color:yellow',
- 'c': 'color:cyan',
- 'm': 'color:magenta',
- 'k': 'color:black'
- }[letter]
+ def colorName(self, color):
+ return f'color:{color}'
def multiMeter(self):
print("Function : MultiMeter")
@@ -533,11 +1141,33 @@ def getRMSValue(self, dataPoints):
getcontext().prec = 5
return np.sqrt(np.mean(np.square(dataPoints)))
+ def eventFilter(self, obj, event):
+ # Forward mouse move events from the canvas to the NavigationToolbar for coordinate updates
+ if obj == self.canvas and event.type() == QtCore.QEvent.MouseMove:
+ QtWidgets.QApplication.sendEvent(self.navToolBar, event)
+ return super().eventFilter(obj, event)
+
+ def update_coordinates(self, event):
+ # Directly update the custom coordinates label
+ if hasattr(self, 'coord_label') and self.coord_label:
+ if event.inaxes:
+ x, y = event.xdata, event.ydata
+ msg = f"x={x:.3f}, y={y:.3f}"
+ self.coord_label.setText(msg)
+ else:
+ self.coord_label.setText("")
+
class MultimeterWidgetClass(QtWidgets.QWidget):
def __init__(self, node_branch, rmsValue, loc_x, loc_y, voltFlag):
QtWidgets.QWidget.__init__(self)
-
+
+ # Get the current theme from the plot window
+ self.is_dark_theme = plotWindow.instance.is_dark_theme if plotWindow.instance else True
+
+ # Apply theme
+ self.setStyleSheet(DARK_MULTIMETER_STYLE if self.is_dark_theme else LIGHT_MULTIMETER_STYLE)
+
self.multimeter = QtWidgets.QWidget(self)
if voltFlag:
self.node_branchLabel = QtWidgets.QLabel("Node")
@@ -549,6 +1179,10 @@ def __init__(self, node_branch, rmsValue, loc_x, loc_y, voltFlag):
self.rmsLabel = QtWidgets.QLabel("RMS Value")
self.nodeBranchValue = QtWidgets.QLabel(str(node_branch))
+ # Set value label class for special styling
+ self.rmsValue.setProperty("class", "value")
+ self.nodeBranchValue.setProperty("class", "value")
+
self.layout = QtWidgets.QGridLayout(self)
self.layout.addWidget(self.node_branchLabel, 0, 0)
self.layout.addWidget(self.rmsLabel, 0, 1)
@@ -562,6 +1196,73 @@ def __init__(self, node_branch, rmsValue, loc_x, loc_y, voltFlag):
self.setWindowFlags(QtCore.Qt.WindowStaysOnTopHint)
self.show()
+ def toggle_theme(self):
+ """Toggle between light and dark themes."""
+ self.is_dark_theme = not self.is_dark_theme
+ self.setStyleSheet(DARK_MULTIMETER_STYLE if self.is_dark_theme else LIGHT_MULTIMETER_STYLE)
+
+ def update_plot_theme(self):
+ """Update plot colors based on current theme."""
+ if self.is_dark_theme:
+ # Dark theme colors
+ bg_color = DARK_BLUE
+ text_color = TEXT_COLOR # Use white for all text
+ accent_color = ACCENT_BLUE
+ grid_color = BORDER_COLOR
+ function_color = TEXT_COLOR # White for dark theme
+ else:
+ # Light theme colors
+ bg_color = LIGHT_BG
+ text_color = LIGHT_TEXT
+ accent_color = LIGHT_ACCENT
+ grid_color = LIGHT_BORDER
+ function_color = LIGHT_TEXT # Black for light theme
+
+ # Update figure and axes colors
+ self.fig.patch.set_facecolor(bg_color)
+ self.axes.set_facecolor(bg_color)
+
+ # Update text colors
+ self.axes.tick_params(colors=text_color, labelsize=12)
+ self.axes.xaxis.label.set_color(text_color)
+ self.axes.yaxis.label.set_color(text_color)
+ self.axes.title.set_color(text_color)
+
+ # Update spines
+ for spine in self.axes.spines.values():
+ spine.set_color(accent_color)
+ spine.set_linewidth(2)
+
+ # Update grid
+ self.axes.grid(True, color=grid_color, alpha=0.3)
+
+ # Update function text colors
+ for text in self.axes.texts:
+ text.set_color(function_color)
+
+ # Update annotation colors
+ for annotation in self.axes.annotations if hasattr(self.axes, 'annotations') else []:
+ annotation.set_color(function_color)
+ # Update all children that are Text (for annotations, etc.)
+ for child in self.axes.get_children():
+ if hasattr(child, 'set_color') and hasattr(child, 'get_text') and child.get_text() != '':
+ child.set_color(function_color)
+
+ # Update legend colors if it exists
+ if self.axes.get_legend():
+ legend = self.axes.get_legend()
+ legend.get_frame().set_facecolor(bg_color)
+ legend.get_frame().set_edgecolor(accent_color)
+ for text in legend.get_texts():
+ text.set_color(text_color)
+
+ # Redraw the canvas
+ self.canvas.draw()
+
+ # Update multimeter themes if they exist
+ for widget in self.findChildren(MultimeterWidgetClass):
+ widget.toggle_theme()
+
class DataExtraction:
def __init__(self):
@@ -656,23 +1357,39 @@ def numberFinder(self, fpath):
def openFile(self, fpath):
try:
+ vfile = os.path.join(fpath, "plot_data_v.txt")
+ if not os.path.exists(vfile):
+ raise FileNotFoundError(f"Missing file: {vfile}")
+ if os.path.getsize(vfile) == 0:
+ raise ValueError(f"File is empty: {vfile}")
+ with open(vfile) as f1:
+ allv = f1.read()
+
+ if not allv.strip():
+ raise ValueError(f"File is empty: {vfile}")
+
+ if len(allv.splitlines()) < 6:
+ raise ValueError(f"File {vfile} does not have enough lines for plotting.")
+
+ if not os.path.exists(os.path.join(fpath, "plot_data_i.txt")):
+ raise FileNotFoundError(f"Missing file: {os.path.join(fpath, 'plot_data_i.txt')}")
with open(os.path.join(fpath, "plot_data_i.txt")) as f2:
alli = f2.read()
+ if not alli.strip():
+ raise ValueError(f"File is empty: {os.path.join(fpath, 'plot_data_i.txt')}")
+
alli = alli.split("\n")
self.NBIList = []
-
- with open(os.path.join(fpath, "plot_data_v.txt")) as f1:
- allv = f1.read()
-
except Exception as e:
print("Exception Message : ", str(e))
self.obj_appconfig.print_error('Exception Message :' + str(e))
self.msg = QtWidgets.QErrorMessage()
self.msg.setModal(True)
self.msg.setWindowTitle("Error Message")
- self.msg.showMessage('Unable to open plot data files.')
+ self.msg.showMessage(f'Unable to open plot data files.\n{str(e)}')
self.msg.exec_()
+ raise # Reraise so caller knows it failed
try:
for l in alli[3].split(" "):
@@ -686,8 +1403,9 @@ def openFile(self, fpath):
self.msg = QtWidgets.QErrorMessage()
self.msg.setModal(True)
self.msg.setWindowTitle("Error Message")
- self.msg.showMessage('Unable to read Analysis File.')
+ self.msg.showMessage(f'Unable to read Analysis File.\n{str(e)}')
self.msg.exec_()
+ raise
d = self.numberFinder(fpath)
d1 = int(d[0] + 1)
diff --git a/src/ngspicetoModelica/ModelicaUI.py b/src/ngspicetoModelica/ModelicaUI.py
index a687bb931..25d440d1a 100755
--- a/src/ngspicetoModelica/ModelicaUI.py
+++ b/src/ngspicetoModelica/ModelicaUI.py
@@ -21,7 +21,13 @@ def __init__(self, dir=None):
self.ngspiceNetlist = os.path.join(
self.projDir, self.projName + ".cir.out")
self.modelicaNetlist = os.path.join(self.projDir, "*.mo")
- self.map_json = Appconfig.modelica_map_json
+
+ # Handle the case where modelica_map_json might be None
+ try:
+ self.map_json = Appconfig.modelica_map_json
+ except AttributeError:
+ self.map_json = None
+ print("Warning: Modelica map JSON not available")
self.grid = QtWidgets.QGridLayout()
self.FileEdit = QtWidgets.QLineEdit()
diff --git a/src/ngspicetoModelica/NgspicetoModelica.py b/src/ngspicetoModelica/NgspicetoModelica.py
index 9a3d504fb..43d2ff8e4 100755
--- a/src/ngspicetoModelica/NgspicetoModelica.py
+++ b/src/ngspicetoModelica/NgspicetoModelica.py
@@ -8,8 +8,17 @@ class NgMoConverter:
def __init__(self, map_json):
# Loading JSON file which hold the mapping information between ngspice
# and Modelica.
- with open(map_json) as mappingFile:
- self.mappingData = json.load(mappingFile)
+ if map_json is None:
+ # Use default mapping if no JSON file is available
+ self.mappingData = {}
+ print("Warning: No mapping JSON file available, using default mapping")
+ else:
+ try:
+ with open(map_json) as mappingFile:
+ self.mappingData = json.load(mappingFile)
+ except (FileNotFoundError, json.JSONDecodeError) as e:
+ print(f"Warning: Could not load mapping JSON file: {e}")
+ self.mappingData = {}
self.ifMOS = False
self.sourceDetail = []
diff --git a/src/projManagement/Kicad.py b/src/projManagement/Kicad.py
index 833f075e0..b045585d9 100644
--- a/src/projManagement/Kicad.py
+++ b/src/projManagement/Kicad.py
@@ -20,7 +20,7 @@
from . import Validation
from configuration.Appconfig import Appconfig
from . import Worker
-from PyQt5 import QtWidgets
+from PyQt5 import QtWidgets, QtGui, QtCore
class Kicad:
@@ -70,6 +70,7 @@ def openSchematic(self):
@params
+
@return
"""
print("Function : Open Kicad Schematic")
@@ -235,3 +236,71 @@ def openKicadToNgspice(self):
self.obj_appconfig.print_warning(
'Please select the project first. You can either ' +
'create new project or open an existing project')
+
+
+class KicadWidget(QtWidgets.QWidget):
+ """
+ A modern, themed QWidget for Kicad actions, matching the Application.py style.
+ This does not affect any backend logic.
+ """
+ def __init__(self, parent=None):
+ super(KicadWidget, self).__init__(parent)
+ self.setObjectName("KicadWidget")
+ self.setStyleSheet("""
+ QWidget#KicadWidget {
+ background: qlineargradient(x1:0, y1:0, x2:1, y2:1,
+ stop:0 #23273a, stop:1 #181b24);
+ border-radius: 16px;
+ border: 1.5px solid #23273a;
+ }
+ QPushButton {
+ background-color: #23273a;
+ color: #e8eaed;
+ border: 1.5px solid #353b48;
+ border-radius: 10px;
+ padding: 10px 24px;
+ font-family: 'Inter', 'Segoe UI', 'Roboto', 'Arial', sans-serif;
+ font-size: 15px;
+ font-weight: 500;
+ letter-spacing: 0.1px;
+ }
+ QPushButton:hover {
+ background-color: #353b48;
+ color: #40c4ff;
+ }
+ QPushButton:pressed {
+ background-color: #181b24;
+ color: #40c4ff;
+ }
+ QLabel {
+ color: #e8eaed;
+ font-size: 18px;
+ font-weight: bold;
+ padding-bottom: 12px;
+ }
+ """)
+ layout = QtWidgets.QVBoxLayout()
+ layout.setSpacing(18)
+ layout.setContentsMargins(32, 32, 32, 32)
+
+ title = QtWidgets.QLabel("Kicad Project Actions")
+ title.setAlignment(QtCore.Qt.AlignCenter)
+ layout.addWidget(title)
+
+ btn_open_schematic = QtWidgets.QPushButton("Open Schematic")
+ btn_open_layout = QtWidgets.QPushButton("Open Layout")
+ btn_open_footprint = QtWidgets.QPushButton("Open Footprint Editor")
+ btn_convert_ngspice = QtWidgets.QPushButton("Convert to Ngspice")
+
+ # These are placeholders for connecting to actual logic
+ btn_open_schematic.clicked.connect(lambda: print("Open Schematic clicked"))
+ btn_open_layout.clicked.connect(lambda: print("Open Layout clicked"))
+ btn_open_footprint.clicked.connect(lambda: print("Open Footprint Editor clicked"))
+ btn_convert_ngspice.clicked.connect(lambda: print("Convert to Ngspice clicked"))
+
+ layout.addWidget(btn_open_schematic)
+ layout.addWidget(btn_open_layout)
+ layout.addWidget(btn_open_footprint)
+ layout.addWidget(btn_convert_ngspice)
+ layout.addStretch(1)
+ self.setLayout(layout)
diff --git a/src/projManagement/Validation.py b/src/projManagement/Validation.py
index 5f239163e..e4f582c5a 100644
--- a/src/projManagement/Validation.py
+++ b/src/projManagement/Validation.py
@@ -61,8 +61,8 @@ def validateNewproj(self, projDir):
:projDir => Contains path of the new projDir created
@return
- :"CHECKEXIST" => If smae project name folder exists
- :"CHECKNAME" => If space is there in name
+ :"CHECKEXIST" => If same project name folder exists
+ :"CHECKNAME" => If space is there in project name
:"VALID" => If valid project name given
"""
print("Function: Validating New Project Information")
@@ -72,7 +72,9 @@ def validateNewproj(self, projDir):
return "CHECKEXIST" # Project with name already exist
else:
# Check Proper name for project. It should not have space
- if re.search(r"\s", projDir):
+ # Extract only the project name (basename) from the full path
+ projName = os.path.basename(projDir)
+ if re.search(r"\s", projName):
return "CHECKNAME"
else:
return "VALID"
@@ -219,4 +221,4 @@ def validateSubcir(self, projDir, fileName):
return True
print("Last line not found:", last_line)
- return False
+ return false
\ No newline at end of file
diff --git a/src/projManagement/newProject.py b/src/projManagement/newProject.py
index 10fb0cb5a..fd2973153 100644
--- a/src/projManagement/newProject.py
+++ b/src/projManagement/newProject.py
@@ -63,8 +63,8 @@ def createProject(self, projName):
self.projName = projName
self.workspace = self.obj_appconfig.default_workspace['workspace']
# self.projName = self.projEdit.text()
- # Remove leading and trailing space
- self.projName = str(self.projName).rstrip().lstrip()
+ # Remove leading and trailing spaces AND replace internal spaces with underscores
+ self.projName = str(self.projName).strip().replace(" ", "_")
self.projDir = os.path.join(self.workspace, str(self.projName))
@@ -144,5 +144,5 @@ def createProject(self, projName):
self.msg.exec_()
return None, None
- def cancelProject(self):
- self.close()
+def cancelProject(self):
+ self.close()
\ No newline at end of file
diff --git a/src/subcircuit/Subcircuit.py b/src/subcircuit/Subcircuit.py
index eb06e145f..0961f36f0 100644
--- a/src/subcircuit/Subcircuit.py
+++ b/src/subcircuit/Subcircuit.py
@@ -29,26 +29,61 @@ def __init__(self, parent=None):
self.splitter = QtWidgets.QSplitter()
self.splitter.setOrientation(QtCore.Qt.Vertical)
+ # Create buttons with proper sizing and styling
self.newbtn = QtWidgets.QPushButton('New Subcircuit Schematic')
self.newbtn.setToolTip('To create new Subcircuit Schematic')
- self.newbtn.setFixedSize(200, 40)
+ self.newbtn.setMinimumWidth(250) # Increased width
+ self.newbtn.setMinimumHeight(45) # Increased height
+ self.newbtn.setStyleSheet("""
+ QPushButton {
+ background: qlineargradient(x1:0, y1:0, x2:0, y2:1,
+ stop:0 #40c4ff, stop:1 #1976d2);
+ color: #181b24;
+ border: 1px solid #40c4ff;
+ border-radius: 10px;
+ padding: 10px 20px;
+ font-size: 14px;
+ font-weight: bold;
+ text-align: center;
+ }
+ QPushButton:hover {
+ background: #1976d2;
+ color: #fff;
+ border: 1.5px solid #1976d2;
+ }
+ QPushButton:pressed {
+ background: #23273a;
+ color: #40c4ff;
+ border: 1.5px solid #40c4ff;
+ }
+ """)
self.newbtn.clicked.connect(self.newsch)
+
self.editbtn = QtWidgets.QPushButton('Edit Subcircuit Schematic')
self.editbtn.setToolTip('To edit existing Subcircuit Schematic')
- self.editbtn.setFixedSize(200, 40)
+ self.editbtn.setMinimumWidth(250) # Increased width
+ self.editbtn.setMinimumHeight(45) # Increased height
+ self.editbtn.setStyleSheet(self.newbtn.styleSheet())
self.editbtn.clicked.connect(self.editsch)
+
self.convertbtn = QtWidgets.QPushButton('Convert Kicad to Ngspice')
- self.convertbtn.setToolTip(
- 'To convert Subcircuit Kicad Netlist to Ngspice Netlist')
- self.convertbtn.setFixedSize(200, 40)
+ self.convertbtn.setToolTip('To convert Subcircuit Kicad Netlist to Ngspice Netlist')
+ self.convertbtn.setMinimumWidth(250) # Increased width
+ self.convertbtn.setMinimumHeight(45) # Increased height
+ self.convertbtn.setStyleSheet(self.newbtn.styleSheet())
self.convertbtn.clicked.connect(self.convertsch)
+
self.uploadbtn = QtWidgets.QPushButton('Upload a Subcircuit')
- self.uploadbtn.setToolTip(
- 'To Upload a subcircuit')
- self.uploadbtn.setFixedSize(180, 38)
+ self.uploadbtn.setToolTip('To Upload a subcircuit')
+ self.uploadbtn.setMinimumWidth(250) # Increased width
+ self.uploadbtn.setMinimumHeight(45) # Increased height
+ self.uploadbtn.setStyleSheet(self.newbtn.styleSheet())
self.uploadbtn.clicked.connect(self.uploadSub)
+ # Create layout with proper spacing
self.hbox = QtWidgets.QHBoxLayout()
+ self.hbox.setSpacing(15) # Add spacing between buttons
+ self.hbox.setContentsMargins(15, 15, 15, 15) # Add margins around buttons
self.hbox.addWidget(self.newbtn)
self.hbox.addWidget(self.editbtn)
self.hbox.addWidget(self.convertbtn)
@@ -56,6 +91,8 @@ def __init__(self, parent=None):
self.hbox.addStretch(1)
self.vbox = QtWidgets.QVBoxLayout()
+ self.vbox.setSpacing(15) # Add vertical spacing
+ self.vbox.setContentsMargins(15, 15, 15, 15) # Add margins
self.vbox.addLayout(self.hbox)
self.vbox.addStretch(1)