Semapp 1.0.5__py3-none-any.whl

This diff represents the content of publicly available package versions that have been released to one of the supported registries. The information contained in this diff is provided for informational purposes only and reflects changes between package versions as they appear in their respective public registries.
@@ -0,0 +1,54 @@
1
+ """
2
+ Manage the scrollbar and the functions for the window size
3
+ """
4
+ from PyQt5.QtCore import Qt, QSize
5
+ from PyQt5.QtWidgets import QScrollArea, QVBoxLayout
6
+ from PyQt5.QtGui import QGuiApplication
7
+
8
+
9
+ class LayoutFrame:
10
+ """Class for setting up layout and scroll area"""
11
+
12
+ def __init__(self, main_window):
13
+ self.main_window = main_window
14
+ self.canvas_widget = None
15
+ self.canvas_layout = None
16
+ self.scroll_area = QScrollArea(main_window)
17
+
18
+ def setup_layout(self, canvas_widget, canvas_layout):
19
+ """Set up layout with scroll area and other settings"""
20
+ self.canvas_widget = canvas_widget
21
+ self.canvas_layout = canvas_layout
22
+
23
+ # Set up the QScrollArea
24
+ self.scroll_area.setWidget(self.canvas_widget)
25
+ self.scroll_area.setWidgetResizable(True)
26
+ self.scroll_area.setHorizontalScrollBarPolicy(Qt.ScrollBarAsNeeded)
27
+ self.scroll_area.setVerticalScrollBarPolicy(Qt.ScrollBarAsNeeded)
28
+
29
+ # Set layout for the main window
30
+ layout = QVBoxLayout(self.main_window)
31
+ layout.addWidget(self.scroll_area)
32
+ self.main_window.setLayout(layout)
33
+
34
+ def set_max_window_size(self):
35
+ """Set the maximum window size based on the screen size"""
36
+ screen_geometry = QGuiApplication.primaryScreen().availableGeometry()
37
+ max_width = screen_geometry.width()
38
+ max_height = screen_geometry.height() - 50
39
+ self.main_window.setMaximumSize(max_width, max_height)
40
+
41
+ def position_window_top_left(self):
42
+ """Position the window at the top-left corner of the screen"""
43
+ screen_geometry = QGuiApplication.primaryScreen().availableGeometry()
44
+ self.main_window.move(screen_geometry.topLeft())
45
+
46
+ def adjust_scroll_area_size(self):
47
+ """Adjust the size of the window based on the content"""
48
+ self.canvas_widget.adjustSize()
49
+ optimal_size = self.canvas_widget.sizeHint()
50
+
51
+ screen_size = QGuiApplication.primaryScreen().availableSize()
52
+ new_size = QSize(min(optimal_size.width(), screen_size.width()) + 50,
53
+ min(optimal_size.height(), screen_size.height()) + 50)
54
+ self.main_window.resize(new_size)
@@ -0,0 +1,170 @@
1
+ """Module for settings window"""
2
+ # pylint: disable=no-name-in-module, trailing-whitespace, too-many-branches, too-many-statements
3
+ import os
4
+ import json
5
+ from PyQt5.QtWidgets import (
6
+ QDialog, QVBoxLayout, QTableWidget, QPushButton,
7
+ QTableWidgetItem
8
+ )
9
+ from PyQt5.QtCore import pyqtSignal
10
+
11
+ class SettingsWindow(QDialog):
12
+ """Class for settings window"""
13
+ data_updated = pyqtSignal() # Signal emitted when data is updated
14
+
15
+ def __init__(self):
16
+ super().__init__()
17
+ self.setWindowTitle("Settings")
18
+
19
+ # Get the user's folder path (C:\Users\XXXXX)
20
+ self.user_folder = os.path.expanduser("~")
21
+
22
+ # Define the new folder "SEM" and the file to store settings
23
+ self.new_folder = os.path.join(self.user_folder, "SEM")
24
+ self.data_file = os.path.join(self.new_folder, "settings_data.json")
25
+ self.data = [] # Structure to store the table data
26
+
27
+ # Set up the UI layout
28
+ self.layout = QVBoxLayout()
29
+ self.setLayout(self.layout)
30
+
31
+ # Create and add the table and buttons to the layout
32
+ self.create_table()
33
+ self.create_buttons()
34
+
35
+ # Load data from the settings file
36
+ self.load_data()
37
+
38
+ def create_table(self):
39
+ """Create the settings table."""
40
+ self.table = QTableWidget()
41
+ self.table.setColumnCount(2)
42
+ self.table.setHorizontalHeaderLabels(["Scale", "Image Type"])
43
+
44
+ # Temporarily block signals to prevent updates during initialization
45
+ self.table.blockSignals(True)
46
+
47
+ # Populate the table with existing data
48
+ for row_data in self.data:
49
+ if "Scale" in row_data and "Image Type" in row_data:
50
+ self.add_row(row_data["Scale"], row_data["Image Type"], update_data=False)
51
+ else:
52
+ print(f"Invalid row data: {row_data}")
53
+
54
+ # Re-enable signals after table population
55
+ self.table.blockSignals(False)
56
+
57
+ # Connect the itemChanged signal to update the data structure
58
+ self.table.itemChanged.connect(self.update_data)
59
+ self.layout.addWidget(self.table)
60
+
61
+ def create_buttons(self):
62
+ """Create buttons to add and remove rows."""
63
+ add_button = QPushButton("Add Row")
64
+ remove_button = QPushButton("Remove Selected Row")
65
+
66
+ add_button.clicked.connect(self.add_row)
67
+ remove_button.clicked.connect(self.remove_selected_row)
68
+
69
+ self.layout.addWidget(add_button)
70
+ self.layout.addWidget(remove_button)
71
+
72
+ def add_row(self, scale="5x5", image_type="Type", update_data=True):
73
+ """Add a new row to the table with default values."""
74
+ row_position = self.table.rowCount()
75
+ self.table.insertRow(row_position)
76
+
77
+ scale_item = QTableWidgetItem(scale)
78
+ image_type_item = QTableWidgetItem(image_type)
79
+
80
+ self.table.setItem(row_position, 0, scale_item)
81
+ self.table.setItem(row_position, 1, image_type_item)
82
+
83
+ if update_data:
84
+ self.data.append({"Scale": scale, "Image Type": image_type})
85
+ print("Row added:", self.data)
86
+
87
+ def remove_selected_row(self):
88
+ """Remove the currently selected row from the table."""
89
+ current_row = self.table.currentRow()
90
+ if current_row != -1:
91
+ self.table.removeRow(current_row)
92
+ del self.data[current_row]
93
+ print("Row removed:", self.data)
94
+
95
+ def update_data(self, item):
96
+ """Update the data structure when a cell value changes."""
97
+ try:
98
+ row = item.row()
99
+ column = item.column()
100
+ value = item.text().strip()
101
+
102
+ while len(self.data) <= row:
103
+ self.data.append({"Scale": "", "Image Type": ""})
104
+
105
+ if column == 0: # Update "Scale"
106
+ if not value.replace('.', '', 1).isdigit():
107
+ print(f"Invalid value for Scale: {value}")
108
+ return
109
+ self.data[row]["Scale"] = value
110
+ elif column == 1: # Update "Image Type"
111
+ self.data[row]["Image Type"] = value
112
+ else:
113
+ print(f"Unexpected column index: {column}")
114
+ return
115
+
116
+ self.save_data()
117
+ print(f"Data updated successfully: {self.data[row]}")
118
+ except Exception as e:
119
+ print(f"Error updating data: {e}")
120
+
121
+ def closeEvent(self, event):
122
+ """Save data to a file when the dialog is closed."""
123
+ self.save_data()
124
+ self.data_updated.emit()
125
+ super().closeEvent(event)
126
+
127
+ def normalize_data(self):
128
+ """Synchronize self.data with the actual table contents."""
129
+ self.data = self.get_table_data()
130
+
131
+ def save_data(self):
132
+ """Save the table data to a JSON file."""
133
+ self.normalize_data()
134
+ with open(self.data_file, "w") as file:
135
+ json.dump(self.data, file, indent=4)
136
+ print("Data saved successfully.")
137
+
138
+ def load_data(self):
139
+ """Load the table data from the JSON file."""
140
+ try:
141
+ with open(self.data_file, "r") as file:
142
+ self.data = json.load(file)
143
+ print("Data loaded successfully:", self.data)
144
+ except FileNotFoundError:
145
+ print("No previous data found. Starting fresh.")
146
+ self.data = []
147
+ except Exception as err:
148
+ print(f"Error loading data: {err}")
149
+ self.data = []
150
+
151
+ self.table.blockSignals(True)
152
+ self.table.setRowCount(0)
153
+ for row_data in self.data:
154
+ self.add_row(row_data.get("Scale", ""),
155
+ row_data.get("Image Type", ""), update_data=False)
156
+ self.table.blockSignals(False)
157
+
158
+ def get_table_data(self):
159
+ """Get the current data from the table as a list of dictionaries."""
160
+ table_data = []
161
+ for row in range(self.table.rowCount()):
162
+ scale_item = self.table.item(row, 0)
163
+ image_type_item = self.table.item(row, 1)
164
+
165
+ if scale_item and image_type_item:
166
+ scale = scale_item.text()
167
+ image_type = image_type_item.text()
168
+ table_data.append({"Scale": scale, "Image Type": image_type})
169
+
170
+ return table_data
@@ -0,0 +1,152 @@
1
+ """Module containing all styles for the GUI"""
2
+
3
+ # Radio button styles
4
+ RADIO_BUTTON_STYLE = """
5
+ QRadioButton {
6
+ spacing: 0px;
7
+ font-size: 14px;
8
+ }
9
+ QRadioButton::indicator {
10
+ width: 20px;
11
+ height: 20px;
12
+ }
13
+ QRadioButton::indicator:checked {
14
+ background-color: #f0ca41;
15
+ border: 2px solid black;
16
+ }
17
+ QRadioButton::indicator:unchecked {
18
+ background-color: white;
19
+ border: 2px solid #ccc;
20
+ }
21
+ """
22
+
23
+ # Settings button style
24
+ SETTINGS_BUTTON_STYLE = """
25
+ QPushButton {
26
+ font-size: 16px;
27
+ background-color: #b3e5fc;
28
+ border: 2px solid #8c8c8c;
29
+ border-radius: 10px;
30
+ padding: 5px;
31
+ height: 100px;
32
+ }
33
+ QPushButton:hover {
34
+ background-color: #64b5f6;
35
+ }
36
+ """
37
+
38
+ # Run button style
39
+ RUN_BUTTON_STYLE = """
40
+ QPushButton {
41
+ font-size: 16px;
42
+ background-color: #ffcc80;
43
+ border: 2px solid #8c8c8c;
44
+ border-radius: 10px;
45
+ padding: 5px;
46
+ height: 100px;
47
+ }
48
+ QPushButton:hover {
49
+ background-color: #ffb74d;
50
+ }
51
+ """
52
+
53
+ # Group box style
54
+ GROUP_BOX_STYLE = """
55
+ QGroupBox {
56
+ border: 1px solid black;
57
+ border-radius: 5px;
58
+ margin-top: 10px;
59
+ font-size: 20px;
60
+ font-weight: bold;
61
+ }
62
+ QGroupBox::title {
63
+ font-size: 14px;
64
+ font-weight: bold;
65
+ subcontrol-origin: margin;
66
+ subcontrol-position: top center;
67
+ }
68
+ """
69
+
70
+ # Wafer button styles
71
+ WAFER_BUTTON_DEFAULT_STYLE = """
72
+ QRadioButton {
73
+ spacing: 0px;
74
+ font-size: 16px;
75
+ }
76
+ QRadioButton::indicator {
77
+ width: 25px;
78
+ height: 25px;
79
+ }
80
+ QRadioButton::indicator:checked {
81
+ background-color: #ccffcc;
82
+ border: 2px solid black;
83
+ }
84
+ QRadioButton::indicator:unchecked {
85
+ background-color: white;
86
+ border: 2px solid #ccc;
87
+ }
88
+ """
89
+
90
+ WAFER_BUTTON_EXISTING_STYLE = """
91
+ QRadioButton {
92
+ spacing: 0px;
93
+ font-size: 16px;
94
+ }
95
+ QRadioButton::indicator {
96
+ width: 25px;
97
+ height: 25px;
98
+ border: 2px solid #ccc;
99
+ background-color: lightblue;
100
+ }
101
+ QRadioButton::indicator:checked {
102
+ background-color: #ccffcc;
103
+ border: 2px solid black;
104
+ }
105
+ QRadioButton::indicator:unchecked {
106
+ background-color: lightblue;
107
+ border: 2px solid #ccc;
108
+ }
109
+ """
110
+
111
+ WAFER_BUTTON_MISSING_STYLE = """
112
+ QRadioButton {
113
+ spacing: 0px;
114
+ font-size: 16px;
115
+ }
116
+ QRadioButton::indicator {
117
+ width: 25px;
118
+ height: 25px;
119
+ border: 2px solid #ccc;
120
+ background-color: lightcoral;
121
+ }
122
+ QRadioButton::indicator:checked {
123
+ background-color: #ccffcc;
124
+ border: 2px solid black;
125
+ }
126
+ QRadioButton::indicator:unchecked {
127
+ background-color: lightcoral;
128
+ border: 2px solid #ccc;
129
+ }
130
+ """
131
+
132
+ # Style for folder/file selection buttons
133
+ SELECT_BUTTON_STYLE = """
134
+ QPushButton {
135
+ font-size: 16px;
136
+ background-color: #b3e5fc;
137
+ border: 2px solid #8c8c8c;
138
+ border-radius: 10px;
139
+ padding: 10px;
140
+ }
141
+ QPushButton:hover {
142
+ background-color: #64b5f6;
143
+ }
144
+ """
145
+
146
+ # Style for path labels
147
+ PATH_LABEL_STYLE = """
148
+ QLabel {
149
+ font-size: 14px;
150
+ padding: 5px;
151
+ }
152
+ """
semapp/Layout/toast.py ADDED
@@ -0,0 +1,157 @@
1
+ """
2
+ Toast notification widget for non-intrusive user feedback.
3
+ """
4
+
5
+ from PyQt5.QtWidgets import QWidget, QLabel, QVBoxLayout
6
+ from PyQt5.QtCore import Qt, QTimer, QPropertyAnimation, QEasingCurve, QPoint
7
+ from PyQt5.QtGui import QFont
8
+
9
+
10
+ class ToastNotification(QWidget):
11
+ """
12
+ A toast notification widget that appears temporarily to show messages.
13
+ Non-intrusive and auto-dismisses after a set duration.
14
+ """
15
+
16
+ def __init__(self, message, parent=None, duration=3000, notification_type="info"):
17
+ """
18
+ Initialize the toast notification.
19
+
20
+ Args:
21
+ message: Text message to display
22
+ parent: Parent widget
23
+ duration: Duration in milliseconds before auto-dismiss
24
+ notification_type: Type of notification ("info", "success", "warning", "error")
25
+ """
26
+ super().__init__(parent)
27
+
28
+ self.duration = duration
29
+ self.notification_type = notification_type
30
+
31
+ # Set window flags for frameless, always on top, tooltip behavior
32
+ self.setWindowFlags(
33
+ Qt.ToolTip |
34
+ Qt.FramelessWindowHint |
35
+ Qt.WindowStaysOnTopHint
36
+ )
37
+
38
+ # Set up styles based on notification type
39
+ self._setup_style()
40
+
41
+ # Create layout and label
42
+ layout = QVBoxLayout()
43
+ layout.setContentsMargins(15, 10, 15, 10)
44
+
45
+ label = QLabel(message)
46
+ label.setWordWrap(True)
47
+ font = QFont()
48
+ font.setPointSize(10)
49
+ label.setFont(font)
50
+ label.setAlignment(Qt.AlignCenter)
51
+
52
+ layout.addWidget(label)
53
+ self.setLayout(layout)
54
+
55
+ # Adjust size to content
56
+ self.adjustSize()
57
+
58
+ def _setup_style(self):
59
+ """Set up the style based on notification type."""
60
+ colors = {
61
+ "info": {
62
+ "bg": "#2196F3",
63
+ "border": "#1976D2"
64
+ },
65
+ "success": {
66
+ "bg": "#4CAF50",
67
+ "border": "#388E3C"
68
+ },
69
+ "warning": {
70
+ "bg": "#FF9800",
71
+ "border": "#F57C00"
72
+ },
73
+ "error": {
74
+ "bg": "#F44336",
75
+ "border": "#D32F2F"
76
+ }
77
+ }
78
+
79
+ color = colors.get(self.notification_type, colors["info"])
80
+
81
+ self.setStyleSheet(f"""
82
+ QWidget {{
83
+ background-color: {color['bg']};
84
+ color: white;
85
+ border: 2px solid {color['border']};
86
+ border-radius: 8px;
87
+ padding: 5px;
88
+ }}
89
+ QLabel {{
90
+ background-color: transparent;
91
+ color: white;
92
+ border: none;
93
+ }}
94
+ """)
95
+
96
+ def show_toast(self):
97
+ """
98
+ Show the toast notification with fade-in animation.
99
+ Auto-dismisses after the specified duration.
100
+ """
101
+ if self.parent():
102
+ # Position relative to parent
103
+ parent_rect = self.parent().geometry()
104
+ x = parent_rect.right() - self.width() - 20
105
+ y = parent_rect.top() + 20
106
+ self.move(x, y)
107
+ else:
108
+ # Center on screen if no parent
109
+ from PyQt5.QtGui import QGuiApplication
110
+ screen = QGuiApplication.primaryScreen().geometry()
111
+ x = (screen.width() - self.width()) // 2
112
+ y = screen.height() - self.height() - 50
113
+ self.move(x, y)
114
+
115
+ # Set initial opacity for fade-in
116
+ self.setWindowOpacity(0.0)
117
+ self.show()
118
+
119
+ # Fade-in animation
120
+ fade_in = QPropertyAnimation(self, b"windowOpacity")
121
+ fade_in.setDuration(300)
122
+ fade_in.setStartValue(0.0)
123
+ fade_in.setEndValue(1.0)
124
+ fade_in.setEasingCurve(QEasingCurve.InOutQuad)
125
+ fade_in.start()
126
+
127
+ # Auto-dismiss after duration
128
+ QTimer.singleShot(self.duration, self._dismiss)
129
+
130
+ def _dismiss(self):
131
+ """Dismiss the toast with fade-out animation."""
132
+ fade_out = QPropertyAnimation(self, b"windowOpacity")
133
+ fade_out.setDuration(300)
134
+ fade_out.setStartValue(1.0)
135
+ fade_out.setEndValue(0.0)
136
+ fade_out.setEasingCurve(QEasingCurve.InOutQuad)
137
+ fade_out.finished.connect(self.close)
138
+ fade_out.start()
139
+
140
+
141
+ def show_toast(parent, message, duration=3000, notification_type="info"):
142
+ """
143
+ Convenience function to show a toast notification.
144
+
145
+ Args:
146
+ parent: Parent widget
147
+ message: Message to display
148
+ duration: Duration in milliseconds
149
+ notification_type: Type of notification
150
+
151
+ Returns:
152
+ ToastNotification: The created toast widget
153
+ """
154
+ toast = ToastNotification(message, parent, duration, notification_type)
155
+ toast.show_toast()
156
+ return toast
157
+
@@ -0,0 +1,8 @@
1
+ """
2
+ Plot package initialization.
3
+ Contains plotting and visualization functionality for SEM data.
4
+ """
5
+
6
+ from .utils import *
7
+
8
+ __all__ = []