tenseleyflow/wulftp / a5baae6

Browse files

refactor, focus fixes, stable

Authored by espadonne
SHA
a5baae6bd5b1e93b9a25de85edc45a0ac17158a2
Parents
7ad6d3f
Tree
70ad7d9

4 changed files

StatusFile+-
M src/wulftp/core/file_transfer.py 27 13
M src/wulftp/ui/__init__.py 12 0
A src/wulftp/ui/dialogs.py 376 0
M src/wulftp/ui/main_window.py 22 82
src/wulftp/core/file_transfer.pymodified
@@ -3,6 +3,7 @@
33
 import os
44
 import stat
55
 from typing import Optional
6
+import posixpath  # For proper remote path handling
67
 
78
 from PyQt6.QtCore import QObject, QRunnable, pyqtSignal
89
 
@@ -81,7 +82,7 @@ class DirectoryTransferWorker(QRunnable):
8182
         count = 0
8283
         try:
8384
             for item in self.sftp.listdir_attr(remote_path):
84
-                item_path = os.path.join(remote_path, item.filename)
85
+                item_path = posixpath.join(remote_path, item.filename)
8586
                 if stat.S_ISDIR(item.st_mode):
8687
                     count += self._count_remote_files(item_path)
8788
                 else:
@@ -96,6 +97,9 @@ class DirectoryTransferWorker(QRunnable):
9697
         if self.total_files == 0:
9798
             self.total_files = self._count_files(local_path)
9899
         
100
+        # Ensure remote_path uses forward slashes
101
+        remote_path = remote_path.replace('\\', '/')
102
+        
99103
         # Create remote directory
100104
         try:
101105
             self.sftp.mkdir(remote_path)
@@ -106,17 +110,22 @@ class DirectoryTransferWorker(QRunnable):
106110
         # Upload contents
107111
         for item in os.listdir(local_path):
108112
             local_item = os.path.join(local_path, item)
109
-            remote_item = os.path.join(remote_path, item).replace('\\', '/')
113
+            # Use posixpath for remote paths to ensure forward slashes
114
+            remote_item = posixpath.join(remote_path, item)
110115
             
111116
             if os.path.isdir(local_item):
112117
                 self._upload_directory(local_item, remote_item)
113118
             else:
114119
                 # Upload file
115
-                self.sftp.put(local_item, remote_item)
116
-                self.processed_files += 1
117
-                if self.total_files > 0:
118
-                    progress = int((self.processed_files / self.total_files) * 100)
119
-                    self.signals.progress.emit(progress)
120
+                try:
121
+                    self.sftp.put(local_item, remote_item)
122
+                    self.processed_files += 1
123
+                    if self.total_files > 0:
124
+                        progress = int((self.processed_files / self.total_files) * 100)
125
+                        self.signals.progress.emit(progress)
126
+                except Exception as e:
127
+                    # Re-raise with more context
128
+                    raise Exception(f"Failed to upload {local_item} to {remote_item}: {str(e)}")
120129
     
121130
     def _download_directory(self, remote_path: str, local_path: str):
122131
         """Recursively download a directory"""
@@ -129,18 +138,23 @@ class DirectoryTransferWorker(QRunnable):
129138
         
130139
         # Download contents
131140
         for item in self.sftp.listdir_attr(remote_path):
132
-            remote_item = os.path.join(remote_path, item.filename).replace('\\', '/')
141
+            # Use posixpath for remote paths
142
+            remote_item = posixpath.join(remote_path, item.filename)
133143
             local_item = os.path.join(local_path, item.filename)
134144
             
135145
             if stat.S_ISDIR(item.st_mode):
136146
                 self._download_directory(remote_item, local_item)
137147
             else:
138148
                 # Download file
139
-                self.sftp.get(remote_item, local_item)
140
-                self.processed_files += 1
141
-                if self.total_files > 0:
142
-                    progress = int((self.processed_files / self.total_files) * 100)
143
-                    self.signals.progress.emit(progress)
149
+                try:
150
+                    self.sftp.get(remote_item, local_item)
151
+                    self.processed_files += 1
152
+                    if self.total_files > 0:
153
+                        progress = int((self.processed_files / self.total_files) * 100)
154
+                        self.signals.progress.emit(progress)
155
+                except Exception as e:
156
+                    # Re-raise with more context
157
+                    raise Exception(f"Failed to download {remote_item} to {local_item}: {str(e)}")
144158
 
145159
 
146160
 class FileTransferManager:
src/wulftp/ui/__init__.pymodified
@@ -9,6 +9,13 @@ from .components import (
99
     FileTransferToolbar,
1010
     FileContextMenu
1111
 )
12
+from .dialogs import (
13
+    FastConfirmDialog,
14
+    FastMessageDialog,
15
+    InlineConfirmBar,
16
+    fast_confirm,
17
+    fast_message
18
+)
1219
 from .main_window import WulFTPClient
1320
 
1421
 __all__ = [
@@ -19,5 +26,10 @@ __all__ = [
1926
     "RemoteFilePane",
2027
     "FileTransferToolbar",
2128
     "FileContextMenu",
29
+    "FastConfirmDialog",
30
+    "FastMessageDialog", 
31
+    "InlineConfirmBar",
32
+    "fast_confirm",
33
+    "fast_message",
2234
     "WulFTPClient"
2335
 ]
src/wulftp/ui/dialogs.pyadded
@@ -0,0 +1,376 @@
1
+"""Fast custom dialogs for wulFTP."""
2
+
3
+from PyQt6.QtCore import Qt, pyqtSignal, QTimer, QPropertyAnimation, QRect
4
+from PyQt6.QtWidgets import (
5
+    QDialog, QVBoxLayout, QHBoxLayout, QLabel, QPushButton,
6
+    QWidget, QFrame, QGraphicsOpacityEffect, QSizePolicy
7
+)
8
+from PyQt6.QtGui import QKeyEvent
9
+import qtawesome as qta
10
+
11
+
12
+class FastConfirmDialog(QDialog):
13
+    """Fast, lightweight confirmation dialog."""
14
+    
15
+    def __init__(self, title: str, message: str, parent=None):
16
+        super().__init__(parent)
17
+        self.setWindowTitle(title)
18
+        self.setModal(True)
19
+        self.setWindowFlags(
20
+            Qt.WindowType.Dialog | 
21
+            Qt.WindowType.FramelessWindowHint |
22
+            Qt.WindowType.WindowStaysOnTopHint
23
+        )
24
+        
25
+        # Make it slightly transparent for modern look
26
+        self.setWindowOpacity(0.95)
27
+        
28
+        # Don't set size yet - let it adjust after setup
29
+        self._setup_ui(title, message)
30
+        
31
+        # Now adjust size based on content
32
+        self.adjustSize()
33
+        # Set minimum size to ensure content fits
34
+        self.setMinimumSize(400, 150)
35
+        # But don't let it get too big
36
+        self.setMaximumSize(600, 400)
37
+        
38
+        # Position at center of parent
39
+        if parent:
40
+            parent_rect = parent.geometry()
41
+            x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
42
+            y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
43
+            self.move(x, y)
44
+    
45
+    def _setup_ui(self, title: str, message: str):
46
+        """Set up the dialog UI."""
47
+        # Main layout
48
+        layout = QVBoxLayout(self)
49
+        layout.setContentsMargins(0, 0, 0, 0)
50
+        
51
+        # Create frame for border
52
+        frame = QFrame()
53
+        frame.setObjectName("confirmFrame")
54
+        frame.setStyleSheet("""
55
+            #confirmFrame {
56
+                background-color: #2b2b2b;
57
+                border: 2px solid #555;
58
+                border-radius: 8px;
59
+            }
60
+        """)
61
+        
62
+        frame_layout = QVBoxLayout(frame)
63
+        frame_layout.setContentsMargins(20, 20, 20, 20)
64
+        frame_layout.setSpacing(10)
65
+        
66
+        # Title
67
+        title_label = QLabel(title)
68
+        title_label.setStyleSheet("font-size: 16px; font-weight: bold; color: #fff;")
69
+        frame_layout.addWidget(title_label)
70
+        
71
+        # Message
72
+        message_label = QLabel(message)
73
+        message_label.setWordWrap(True)
74
+        message_label.setStyleSheet("color: #ddd; margin: 10px 0;")
75
+        # Ensure the label can expand as needed
76
+        message_label.setSizePolicy(
77
+            QSizePolicy.Policy.Expanding,
78
+            QSizePolicy.Policy.Expanding
79
+        )
80
+        frame_layout.addWidget(message_label)
81
+        
82
+        frame_layout.addStretch()
83
+        
84
+        # Buttons
85
+        button_layout = QHBoxLayout()
86
+        button_layout.addStretch()
87
+        
88
+        # Yes button (default)
89
+        self.yes_btn = QPushButton("Yes")
90
+        self.yes_btn.setDefault(True)
91
+        self.yes_btn.setStyleSheet("""
92
+            QPushButton {
93
+                background-color: #0d7377;
94
+                color: white;
95
+                border: none;
96
+                padding: 8px 20px;
97
+                border-radius: 4px;
98
+                font-weight: bold;
99
+                min-width: 80px;
100
+            }
101
+            QPushButton:hover {
102
+                background-color: #14b8bd;
103
+            }
104
+            QPushButton:pressed {
105
+                background-color: #0a5d61;
106
+            }
107
+        """)
108
+        self.yes_btn.clicked.connect(self.accept)
109
+        button_layout.addWidget(self.yes_btn)
110
+        
111
+        # No button
112
+        self.no_btn = QPushButton("No")
113
+        self.no_btn.setStyleSheet("""
114
+            QPushButton {
115
+                background-color: #555;
116
+                color: white;
117
+                border: none;
118
+                padding: 8px 20px;
119
+                border-radius: 4px;
120
+                font-weight: bold;
121
+                min-width: 80px;
122
+            }
123
+            QPushButton:hover {
124
+                background-color: #666;
125
+            }
126
+            QPushButton:pressed {
127
+                background-color: #444;
128
+            }
129
+        """)
130
+        self.no_btn.clicked.connect(self.reject)
131
+        button_layout.addWidget(self.no_btn)
132
+        
133
+        frame_layout.addLayout(button_layout)
134
+        layout.addWidget(frame)
135
+        
136
+        # Focus on Yes button
137
+        self.yes_btn.setFocus()
138
+    
139
+    def keyPressEvent(self, event: QKeyEvent):
140
+        """Handle key presses for quick response."""
141
+        if event.key() == Qt.Key.Key_Y:
142
+            self.accept()
143
+        elif event.key() == Qt.Key.Key_N or event.key() == Qt.Key.Key_Escape:
144
+            self.reject()
145
+        else:
146
+            super().keyPressEvent(event)
147
+
148
+
149
+class InlineConfirmBar(QWidget):
150
+    """Inline confirmation bar that slides in from top."""
151
+    
152
+    confirmed = pyqtSignal()
153
+    cancelled = pyqtSignal()
154
+    
155
+    def __init__(self, message: str, parent=None):
156
+        super().__init__(parent)
157
+        self.setAutoFillBackground(True)
158
+        self.setFixedHeight(50)
159
+        
160
+        # Style
161
+        self.setStyleSheet("""
162
+            InlineConfirmBar {
163
+                background-color: #3a3a3a;
164
+                border-bottom: 2px solid #0d7377;
165
+            }
166
+        """)
167
+        
168
+        # Layout
169
+        layout = QHBoxLayout(self)
170
+        layout.setContentsMargins(20, 10, 20, 10)
171
+        
172
+        # Icon
173
+        icon_label = QLabel()
174
+        icon_label.setPixmap(
175
+            qta.icon("fa5s.question-circle", color="#0d7377").pixmap(24, 24)
176
+        )
177
+        layout.addWidget(icon_label)
178
+        
179
+        # Message
180
+        message_label = QLabel(message)
181
+        message_label.setStyleSheet("color: #fff; font-size: 14px; margin-left: 10px;")
182
+        layout.addWidget(message_label)
183
+        
184
+        layout.addStretch()
185
+        
186
+        # Buttons
187
+        confirm_btn = QPushButton("Confirm")
188
+        confirm_btn.setStyleSheet("""
189
+            QPushButton {
190
+                background-color: #0d7377;
191
+                color: white;
192
+                border: none;
193
+                padding: 6px 16px;
194
+                border-radius: 3px;
195
+                font-size: 13px;
196
+            }
197
+            QPushButton:hover {
198
+                background-color: #14b8bd;
199
+            }
200
+        """)
201
+        confirm_btn.clicked.connect(self._on_confirm)
202
+        layout.addWidget(confirm_btn)
203
+        
204
+        cancel_btn = QPushButton("Cancel")
205
+        cancel_btn.setStyleSheet("""
206
+            QPushButton {
207
+                background-color: transparent;
208
+                color: #ccc;
209
+                border: 1px solid #555;
210
+                padding: 6px 16px;
211
+                border-radius: 3px;
212
+                font-size: 13px;
213
+                margin-left: 8px;
214
+            }
215
+            QPushButton:hover {
216
+                background-color: #444;
217
+            }
218
+        """)
219
+        cancel_btn.clicked.connect(self._on_cancel)
220
+        layout.addWidget(cancel_btn)
221
+        
222
+        # Set initial position (hidden above parent)
223
+        self.move(0, -self.height())
224
+        
225
+    def _on_confirm(self):
226
+        """Handle confirm button."""
227
+        self.confirmed.emit()
228
+        self.hide_animated()
229
+    
230
+    def _on_cancel(self):
231
+        """Handle cancel button."""
232
+        self.cancelled.emit()
233
+        self.hide_animated()
234
+    
235
+    def show_animated(self):
236
+        """Slide in from top."""
237
+        self.show()
238
+        self.raise_()
239
+        
240
+        # Animate slide in
241
+        self.animation = QPropertyAnimation(self, b"geometry")
242
+        self.animation.setDuration(150)  # Fast animation
243
+        self.animation.setStartValue(QRect(0, -self.height(), self.parent().width(), self.height()))
244
+        self.animation.setEndValue(QRect(0, 0, self.parent().width(), self.height()))
245
+        self.animation.start()
246
+    
247
+    def hide_animated(self):
248
+        """Slide out to top."""
249
+        self.animation = QPropertyAnimation(self, b"geometry")
250
+        self.animation.setDuration(150)
251
+        self.animation.setStartValue(self.geometry())
252
+        self.animation.setEndValue(QRect(0, -self.height(), self.parent().width(), self.height()))
253
+        self.animation.finished.connect(self.hide)
254
+        self.animation.start()
255
+
256
+
257
+class FastMessageDialog(QDialog):
258
+    """Fast message/error dialog."""
259
+    
260
+    def __init__(self, title: str, message: str, icon_name: str = "fa5s.info-circle", 
261
+                 icon_color: str = "#17a2b8", parent=None):
262
+        super().__init__(parent)
263
+        self.setWindowTitle(title)
264
+        self.setModal(True)
265
+        self.setWindowFlags(
266
+            Qt.WindowType.Dialog | 
267
+            Qt.WindowType.FramelessWindowHint |
268
+            Qt.WindowType.WindowStaysOnTopHint
269
+        )
270
+        
271
+        self.setWindowOpacity(0.95)
272
+        
273
+        self._setup_ui(title, message, icon_name, icon_color)
274
+        
275
+        # Adjust size based on content
276
+        self.adjustSize()
277
+        self.setMinimumSize(350, 120)
278
+        self.setMaximumSize(500, 300)
279
+        
280
+        # Center on parent
281
+        if parent:
282
+            parent_rect = parent.geometry()
283
+            x = parent_rect.x() + (parent_rect.width() - self.width()) // 2
284
+            y = parent_rect.y() + (parent_rect.height() - self.height()) // 2
285
+            self.move(x, y)
286
+    
287
+    def _setup_ui(self, title: str, message: str, icon_name: str, icon_color: str):
288
+        """Set up the dialog UI."""
289
+        layout = QVBoxLayout(self)
290
+        layout.setContentsMargins(0, 0, 0, 0)
291
+        
292
+        # Frame
293
+        frame = QFrame()
294
+        frame.setStyleSheet("""
295
+            QFrame {
296
+                background-color: #2b2b2b;
297
+                border: 2px solid #555;
298
+                border-radius: 8px;
299
+            }
300
+        """)
301
+        
302
+        frame_layout = QVBoxLayout(frame)
303
+        frame_layout.setContentsMargins(20, 20, 20, 20)
304
+        
305
+        # Header with icon
306
+        header_layout = QHBoxLayout()
307
+        
308
+        icon_label = QLabel()
309
+        icon_label.setPixmap(qta.icon(icon_name, color=icon_color).pixmap(24, 24))
310
+        header_layout.addWidget(icon_label)
311
+        
312
+        title_label = QLabel(title)
313
+        title_label.setStyleSheet("font-size: 15px; font-weight: bold; color: #fff; margin-left: 8px;")
314
+        header_layout.addWidget(title_label)
315
+        header_layout.addStretch()
316
+        
317
+        frame_layout.addLayout(header_layout)
318
+        
319
+        # Message
320
+        message_label = QLabel(message)
321
+        message_label.setWordWrap(True)
322
+        message_label.setStyleSheet("color: #ddd; margin: 15px 0;")
323
+        frame_layout.addWidget(message_label)
324
+        
325
+        frame_layout.addStretch()
326
+        
327
+        # OK button
328
+        ok_btn = QPushButton("OK")
329
+        ok_btn.setDefault(True)
330
+        ok_btn.setStyleSheet("""
331
+            QPushButton {
332
+                background-color: #555;
333
+                color: white;
334
+                border: none;
335
+                padding: 6px 20px;
336
+                border-radius: 4px;
337
+            }
338
+            QPushButton:hover {
339
+                background-color: #666;
340
+            }
341
+        """)
342
+        ok_btn.clicked.connect(self.accept)
343
+        
344
+        btn_layout = QHBoxLayout()
345
+        btn_layout.addStretch()
346
+        btn_layout.addWidget(ok_btn)
347
+        frame_layout.addLayout(btn_layout)
348
+        
349
+        layout.addWidget(frame)
350
+        
351
+        # Auto-close after 3 seconds
352
+        QTimer.singleShot(3000, self.accept)
353
+    
354
+    def keyPressEvent(self, event: QKeyEvent):
355
+        """Close on any key press."""
356
+        self.accept()
357
+
358
+
359
+def fast_confirm(parent, title: str, message: str) -> bool:
360
+    """Show fast confirmation dialog and return result."""
361
+    dialog = FastConfirmDialog(title, message, parent)
362
+    return dialog.exec() == QDialog.DialogCode.Accepted
363
+
364
+
365
+def fast_message(parent, title: str, message: str, message_type: str = "info"):
366
+    """Show fast message dialog."""
367
+    icon_configs = {
368
+        "info": ("fa5s.info-circle", "#17a2b8"),
369
+        "warning": ("fa5s.exclamation-triangle", "#ffc107"),
370
+        "error": ("fa5s.times-circle", "#dc3545"),
371
+        "success": ("fa5s.check-circle", "#28a745")
372
+    }
373
+    
374
+    icon_name, icon_color = icon_configs.get(message_type, icon_configs["info"])
375
+    dialog = FastMessageDialog(title, message, icon_name, icon_color, parent)
376
+    dialog.exec()
src/wulftp/ui/main_window.pymodified
@@ -39,6 +39,7 @@ from ..ui.components import (
3939
     RemoteFilePane,
4040
     FileContextMenu
4141
 )
42
+from ..ui.dialogs import fast_confirm, fast_message
4243
 
4344
 
4445
 class WulFTPClient(QMainWindow):
@@ -162,7 +163,7 @@ class WulFTPClient(QMainWindow):
162163
         self.remote_pane.navigateHome.connect(self.remote_pane.navigate_home)
163164
         self.remote_pane.createDirectory.connect(lambda: self._create_directory(False))
164165
         self.remote_pane.model.errorOccurred.connect(
165
-            lambda err: QMessageBox.warning(self, "Error", err)
166
+            lambda err: fast_message(self, "Error", err, "error")
166167
         )
167168
         self.remote_pane.model.filesDropped.connect(self._handle_files_dropped)
168169
         self.remote_pane.tree_view.selectionModel().selectionChanged.connect(self._update_button_states)
@@ -242,14 +243,8 @@ class WulFTPClient(QMainWindow):
242243
             ))
243244
 
244245
         except Exception as e:
245
-            QMessageBox.critical(
246
-                self, 
247
-                "Connection Error", 
248
-                str(e)
249
-            )
246
+            fast_message(self, "Connection Error", str(e), "error")
250247
             self._update_connection_state(False)
251
-            # Restore focus after error dialog
252
-            self._restore_focus()
253248
 
254249
     def disconnect(self):
255250
         """Close SFTP connection."""
@@ -323,18 +318,10 @@ class WulFTPClient(QMainWindow):
323318
         
324319
         # Handle directories first
325320
         if directories:
326
-            reply = QMessageBox.question(
327
-                self,
328
-                "Upload Directories",
329
-                format_status_message("Upload", len(directories), "directory") + "?\n\n" +
330
-                "This will recursively upload all contents.",
331
-                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
332
-                QMessageBox.StandardButton.Yes
333
-            )
321
+            msg = format_status_message("Upload", len(directories), "directory") + "?\n\n" + \
322
+                  "This will recursively upload all contents."
334323
             
335
-            self._restore_focus()  # Restore focus after dialog
336
-            
337
-            if reply == QMessageBox.StandardButton.Yes:
324
+            if fast_confirm(self, "Upload Directories", msg):
338325
                 for dir_path in directories:
339326
                     dirname = os.path.basename(dir_path)
340327
                     remote_base = self.remote_pane.model.rootPath()
@@ -374,16 +361,10 @@ class WulFTPClient(QMainWindow):
374361
         
375362
         # Handle directories first
376363
         if directories:
377
-            reply = QMessageBox.question(
378
-                self,
379
-                "Download Directories",
380
-                format_status_message("Download", len(directories), "directory") + "?\n\n" +
381
-                "This will recursively download all contents.",
382
-                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
383
-                QMessageBox.StandardButton.Yes
384
-            )
364
+            msg = format_status_message("Download", len(directories), "directory") + "?\n\n" + \
365
+                  "This will recursively download all contents."
385366
             
386
-            if reply == QMessageBox.StandardButton.Yes:
367
+            if fast_confirm(self, "Download Directories", msg):
387368
                 for dir_path, dirname in directories:
388369
                     local_base = self.local_pane.model.filePath(self.local_pane.tree_view.rootIndex())
389370
                     local_path = os.path.join(local_base, dirname)
@@ -458,11 +439,7 @@ class WulFTPClient(QMainWindow):
458439
     def _transfer_error(self, error):
459440
         """Handle transfer error."""
460441
         self.progress_bar.setVisible(False)
461
-        QMessageBox.critical(
462
-            self, 
463
-            "Transfer Error", 
464
-            error
465
-        )
442
+        fast_message(self, "Transfer Error", error, "error")
466443
 
467444
     def _handle_files_dropped(self, local_paths: List[str], target_directory: str):
468445
         """Handle files and directories dropped onto remote view."""
@@ -483,16 +460,10 @@ class WulFTPClient(QMainWindow):
483460
         
484461
         # Handle directories
485462
         if directories:
486
-            reply = QMessageBox.question(
487
-                self,
488
-                "Upload Directories",
489
-                format_status_message("Upload", len(directories), "directory") + "?\n\n" +
490
-                "This will recursively upload all contents.",
491
-                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
492
-                QMessageBox.StandardButton.Yes
493
-            )
463
+            msg = format_status_message("Upload", len(directories), "directory") + "?\n\n" + \
464
+                  "This will recursively upload all contents."
494465
             
495
-            if reply == QMessageBox.StandardButton.Yes:
466
+            if fast_confirm(self, "Upload Directories", msg):
496467
                 for dir_path in directories:
497468
                     dirname = os.path.basename(dir_path)
498469
                     remote_path = os.path.join(target_directory, dirname).replace('\\', '/')
@@ -546,16 +517,10 @@ class WulFTPClient(QMainWindow):
546517
         
547518
         # Handle directories
548519
         if directories:
549
-            reply = QMessageBox.question(
550
-                self,
551
-                "Download Directories",
552
-                format_status_message("Download", len(directories), "directory") + "?\n\n" +
553
-                "This will recursively download all contents.",
554
-                QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
555
-                QMessageBox.StandardButton.Yes
556
-            )
520
+            msg = format_status_message("Download", len(directories), "directory") + "?\n\n" + \
521
+                  "This will recursively download all contents."
557522
             
558
-            if reply == QMessageBox.StandardButton.Yes:
523
+            if fast_confirm(self, "Download Directories", msg):
559524
                 for remote_path, dirname in directories:
560525
                     local_path = os.path.join(local_directory, dirname)
561526
                     
@@ -622,11 +587,7 @@ class WulFTPClient(QMainWindow):
622587
                             AppConfig.STATUS_MESSAGE_TIMEOUT
623588
                         )
624589
             except Exception as e:
625
-                QMessageBox.warning(
626
-                    self, 
627
-                    "Error", 
628
-                    f"Failed to create directory: {str(e)}"
629
-                )
590
+                fast_message(self, "Error", f"Failed to create directory: {str(e)}", "error")
630591
     
631592
     def _delete_selected(self, is_local: bool):
632593
         """Delete selected files/directories."""
@@ -647,18 +608,7 @@ class WulFTPClient(QMainWindow):
647608
         if len(items) > 5:
648609
             msg += f"... and {len(items) - 5} more"
649610
         
650
-        reply = QMessageBox.question(
651
-            self,
652
-            "Confirm Delete",
653
-            msg,
654
-            QMessageBox.StandardButton.Yes | QMessageBox.StandardButton.No,
655
-            QMessageBox.StandardButton.No
656
-        )
657
-        
658
-        # Always restore focus after the dialog, regardless of the answer
659
-        self._restore_focus()
660
-        
661
-        if reply == QMessageBox.StandardButton.Yes:
611
+        if fast_confirm(self, "Confirm Delete", msg):
662612
             errors = []
663613
             for path, is_dir, name in items:
664614
                 try:
@@ -677,13 +627,9 @@ class WulFTPClient(QMainWindow):
677627
                     errors.append(f"{name}: {str(e)}")
678628
             
679629
             if errors:
680
-                QMessageBox.warning(
681
-                    self,
682
-                    "Delete Errors",
683
-                    "Some items could not be deleted:\n\n" + "\n".join(errors[:10])
684
-                )
685
-                # Restore focus after error dialog
686
-                self._restore_focus()
630
+                fast_message(self, "Delete Errors", 
631
+                           "Some items could not be deleted:\n\n" + "\n".join(errors[:10]), 
632
+                           "error")
687633
             else:
688634
                 self.status_bar.showMessage(
689635
                     AppConfig.MSG_DELETE_COMPLETE.format(len(items)), 
@@ -721,10 +667,4 @@ class WulFTPClient(QMainWindow):
721667
         elif remote_selection:
722668
             self._delete_selected(False)
723669
         else:
724
-            QMessageBox.information(
725
-                self,
726
-                "Delete",
727
-                "Please select items to delete"
728
-            )
729
-            # Restore focus after info dialog
730
-            self._restore_focus()
670
+            fast_message(self, "Delete", "Please select items to delete", "info")