Module saePisan.service.utils.utils

Functions

def add_copy_context_menu_to_table(table_view)
Expand source code
def add_copy_context_menu_to_table(table_view):
    """
    Adds a context menu to the QTableView for copying data (including headers and content).
    Args:
        table_view (QTableView): The table view to which the context menu will be added.
    """
    def copy_table_data():
        """
        Copies the table data (headers and content) to the clipboard.
        """
        model = table_view.model()
        if model is None:
            return

        # Get headers
        headers = [model.headerData(i, Qt.Orientation.Horizontal) for i in range(model.columnCount())]
        data = '\t'.join(headers) + '\n'

        # Get table content
        for row in range(model.rowCount()):
            row_data = [model.index(row, col).data() for col in range(model.columnCount())]
            data += '\t'.join(map(str, row_data)) + '\n'

        # Copy to clipboard
        clipboard = QApplication.clipboard()
        clipboard.setText(data)

    # Add context menu
    table_view.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
    table_view.customContextMenuRequested.connect(lambda pos: show_table_context_menu(pos, table_view, copy_table_data))
    
    header = table_view.horizontalHeader()
    header.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
    header.customContextMenuRequested.connect(lambda pos: show_table_context_menu(pos, table_view, copy_table_data))

Adds a context menu to the QTableView for copying data (including headers and content).

Args

table_view : QTableView
The table view to which the context menu will be added.
def check_script(r_script)
Expand source code
def check_script(r_script):
    """
    Checks if the provided R script string is not empty.
    Parameters:
    r_script (str): The R script string to check.
    Returns:
    bool: True if the R script string is not empty, False otherwise.
    """
    
    if r_script == "":
        QMessageBox.warning(None, "Warning", "R sript cannot empty")
        return False
    return True

Checks if the provided R script string is not empty. Parameters: r_script (str): The R script string to check. Returns: bool: True if the R script string is not empty, False otherwise.

def copy_output_image(parent, image_label)
Expand source code
def copy_output_image(parent, image_label):
    """Menyalin gambar dari QLabel ke clipboard dengan simpan file sementara (tanpa loop)."""
    pixmap = image_label.pixmap()
    
    if pixmap and not pixmap.isNull():
        temp_folder = os.path.join(parent.path, 'temp')
        temp_path = os.path.join(temp_folder, 'temp_image.png')

        os.makedirs(temp_folder, exist_ok=True)

        if pixmap.save(temp_path):
            print(f"Gambar disimpan di: {temp_path}")
            
            clipboard = QApplication.clipboard()
            clipboard.setPixmap(QPixmap(temp_path))

            if os.path.exists(temp_path):  
                os.remove(temp_path)
        else:
            print("Gagal menyimpan gambar sementara.")
    else:
        print("Tidak ada gambar untuk disalin.")

Menyalin gambar dari QLabel ke clipboard dengan simpan file sementara (tanpa loop).

def delete_all_outputs(parent)
Expand source code
def delete_all_outputs(parent):
    """Removes all output cards from the output layout"""
    while parent.output_layout.count() > 0:
        item = parent.output_layout.takeAt(0)
        widget = item.widget()
        if widget is not None:
            widget.deleteLater()
    parent.data = []  # Clear stored data

Removes all output cards from the output layout

def delete_output_card(parent, card_frame)
Expand source code
def delete_output_card(parent, card_frame):
    """Delete the selected output card"""
    parent.output_layout.removeWidget(card_frame)
    card_frame.deleteLater()

    # Optional: remove from parent.data list
    if hasattr(parent, "data") and isinstance(parent.data, list):
        index = parent.output_layout.indexOf(card_frame)
        if 0 <= index < len(parent.data):
            del parent.data[index]

Delete the selected output card

def display_script_and_output(parent, r_script, results, plot_paths=None, timestamps=None)
Expand source code
def display_script_and_output(parent, r_script, results, plot_paths=None, timestamps=None):
    """
    Adds a new output card to the layout displaying the provided R script and its result.
    Parameters:
    parent (object): The parent widget containing the layout to which the card will be added.
    r_script (str): The R script to be displayed in the card.
    results (dict or str): The output result of the R script to be displayed in the card. If empty or None, only the script will be displayed.
    plot_paths (list): List of paths to plot images to be displayed.
    Returns:
    None
    """
    # Membuat frame sebagai card
    card_frame = QFrame()
    card_frame.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
    card_frame.customContextMenuRequested.connect(lambda pos: show_context_menu(parent, pos, card_frame))
    card_frame.setStyleSheet("""
        QFrame {
            background-color: #f9f9f9;
            border: 1px solid #ddd;
            border-radius: 8px;
            padding: 10px;
        }
        QFrame:hover {
            background-color: #f0f0f0;
            border: 1px solid #c35112;
        }
    """)

    card_frame.setSizePolicy(QSizePolicy.Policy.Preferred, QSizePolicy.Policy.Maximum)

    # Layout vertikal untuk card
    card_layout = QVBoxLayout(card_frame)
    card_layout.setSpacing(8)

    # Bagian Script
    label_script = QLabel("<b>R Script:</b>")
    label_script.setStyleSheet("color: #333; margin-bottom: 5px;")
    out = {}
    out["r_script"] = r_script  # Simpan r_script ke dalam data parent untuk referensi
    result = {}
    # Bersihkan baris kosong di akhir script
    lines = r_script.splitlines()
    while lines and lines[-1].strip() == "":
        lines.pop()
    cleaned_script = "\n".join(lines)

    script_box = QTextEdit()
    script_box.setPlainText(cleaned_script)
    script_box.setReadOnly(True)
    script_box.setStyleSheet("""
        QTextEdit {
            background-color: #fff;
            border: 1px solid #ccc;
            border-radius: 4px;
            padding: 5px;
            font-family: "Courier New", Courier, monospace;
            font-size: 9pt;
        }
    """)
    max_height = 400
    calculated_height = script_box.fontMetrics().lineSpacing() * (cleaned_script.count('\n') + 2)
    script_box.setFixedHeight(min(calculated_height, max_height))
    script_box.setVerticalScrollBarPolicy(Qt.ScrollBarPolicy.ScrollBarAlwaysOn if calculated_height > max_height else Qt.ScrollBarPolicy.ScrollBarAlwaysOff)

    card_layout.addWidget(label_script)
    card_layout.addWidget(script_box)
    # Bagian Output (jika ada)
    if isinstance(results, dict):
        label_output = QLabel("<b>Output:</b>")
        label_output.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
        label_output.setStyleSheet("color: #333; margin-top: 10px; margin-bottom: 5px;")
        card_layout.addWidget(label_output)
        
        if "Model" in results:
            model_value = results["Model"]
            model_label = QLabel(f"<b>Summary of {model_value}</b>")
            model_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
            model_label.setAlignment(Qt.AlignmentFlag.AlignCenter)
            model_label.setStyleSheet("font-size: 20px; color: #333; margin-top: 5px;")
            card_layout.addWidget(model_label)
            result["Model"] = results["Model"]

        # Add timestamp for generation
        timestamp = datetime.now().strftime("%H:%M:%S %d-%m-%Y") if timestamps is None else timestamps
        out["timestamp"] = timestamp
        timestamp_label = QLabel(f"<i>Generated at: {timestamp}</i>")
        timestamp_label.setAlignment(Qt.AlignmentFlag.AlignRight)
        timestamp_label.setStyleSheet("font-size: 12px; color: #666; margin-top: 10px;")
        card_layout.addWidget(timestamp_label)

        for key, value in results.items():
            # Skip displaying if the key is "Model"
            if key == "Model":
                continue
            elif key=="Plot":
                continue

            # Tambahkan header untuk setiap key
            key_label = QLabel(f"<b>{key}:</b>")
            key_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
            key_label.setStyleSheet("color: #333; margin-top: 5px;")
            card_layout.addWidget(key_label)

            if isinstance(value, pl.DataFrame):
                table_view = QTableView()
                model = QStandardItemModel()
                model.setHorizontalHeaderLabels(value.columns)

                for row in value.iter_rows(named=True):
                    items = [QStandardItem(str(row[col])) for col in value.columns]
                    model.appendRow(items)
                
                result[key] = value.to_dict(as_series=False)
                
                table_view.setModel(model)
                table_view.setEditTriggers(QAbstractItemView.EditTrigger.NoEditTriggers)
                table_view.horizontalHeader().setSectionResizeMode(QHeaderView.ResizeMode.Stretch)
                table_view.horizontalHeader().setFixedHeight(50)

                # Hitung tinggi tabel berdasarkan jumlah baris
                row_height = table_view.verticalHeader().defaultSectionSize()
                header_height = table_view.horizontalHeader().height()
                total_height = row_height * model.rowCount() + header_height + 20  # Tambahkan margin
                table_view.setFixedHeight(total_height)

                table_view.setStyleSheet("""
                    QTableView {
                        background-color: #fff;
                        border: 1px solid #ccc;
                        border-radius: 4px;
                        padding: 5px;
                        font-family: Consolas, Courier New, monospace;
                    }
                """)
                add_copy_context_menu_to_table(table_view)
                card_layout.addWidget(table_view)
                
            else:
                # Tampilkan nilai biasa sebagai teks
                value_label = QLabel(str(value))
                value_label.setTextInteractionFlags(Qt.TextInteractionFlag.TextSelectableByMouse)
                value_label.setStyleSheet("color: #333; margin-left: 10px;")
                card_layout.addWidget(value_label)
                result[key] = value
        out["result"] = result  # Simpan hasil ke dalam data parent untuk referensi

    elif results:
        label_output = QLabel("<b>Output:</b>")
        label_output.setStyleSheet("color: #333; margin-top: 10px; margin-bottom: 5px;")
        result_box = QTextEdit()
        result_box.setPlainText(results)
        result_box.setReadOnly(True)
        result_box.setStyleSheet("""
            QTextEdit {
                background-color: #fff;
                border: 1px solid #ccc;
                border-radius: 4px;
                padding: 5px;
                font-family: Consolas, Courier New, monospace;
            }
        """)
        
        card_layout.addWidget(label_output)
        card_layout.addWidget(result_box)

    if plot_paths is not None:
        label_plot = QLabel("<b>Plot:</b>")
        label_plot.setStyleSheet("color: #333; margin-top: 10px; margin-bottom: 5px;")
        card_layout.addWidget(label_plot)

        for plot_path in plot_paths:
            if os.path.exists(plot_path):
                pixmap = QPixmap(plot_path)
                label = QLabel()
                label.setPixmap(pixmap)
                label.setFixedSize(800, 600)
                label.setScaledContents(True)
                label.setStyleSheet("border: 1px solid #ccc; border-radius: 4px;")

                # Enable right-click menu on image
                label.setContextMenuPolicy(Qt.ContextMenuPolicy.CustomContextMenu)
                label.customContextMenuRequested.connect(
                    lambda pos, l=label, p=plot_path: show_image_context_menu(parent, pos, l, p)
                )

                card_layout.addWidget(label)

                # Simpan image ke path baru
                dest_folder = os.path.join(parent.path, 'file-data', 'temp')
                os.makedirs(dest_folder, exist_ok=True)
                unique_id = uuid.uuid4().hex
                filename, ext = os.path.splitext(os.path.basename(plot_path))
                dest_path = os.path.join(dest_folder, f"{filename}_{unique_id}{ext}")
                pixmap.save(dest_path)
                if "Plot" not in result:
                    result["Plot"] = []
                result["Plot"].append(dest_path)
                os.remove(plot_path)
                
    # Tambahkan card ke layout utama
    parent.output_layout.addWidget(card_frame)
    # parent.output_layout.addStretch()
    parent.tab_widget.setCurrentWidget(parent.tab3)
    if not hasattr(parent, "data") or not isinstance(parent.data, list):
        parent.data = []
    parent.data.append(out)
    parent.autosave_data()

Adds a new output card to the layout displaying the provided R script and its result. Parameters: parent (object): The parent widget containing the layout to which the card will be added. r_script (str): The R script to be displayed in the card. results (dict or str): The output result of the R script to be displayed in the card. If empty or None, only the script will be displayed. plot_paths (list): List of paths to plot images to be displayed. Returns: None

def show_context_menu(parent, pos, card_frame)
Expand source code
def show_context_menu(parent, pos, card_frame):
    """Display right-click menu on each output card"""
    menu = QMenu(parent)

    copy_image_action = menu.addAction("Copy Output Image")
    delete_action = menu.addAction("Delete Output")
    delete_all_action = menu.addAction("Delete All Outputs")

    action = menu.exec(card_frame.mapToGlobal(pos))

    if action == copy_image_action:
        parent.copy_output_image(card_frame)
    elif action == delete_action:
        delete_output_card(parent, card_frame)
    elif action == delete_all_action:
        delete_all_outputs(parent)

Display right-click menu on each output card

def show_image_context_menu(parent, pos, image_label, image_path)
Expand source code
def show_image_context_menu(parent, pos, image_label, image_path):
    """Display right-click menu on image labels."""
    menu = QMenu(parent)

    copy_image_action = menu.addAction("Copy Image")
    save_as_action = menu.addAction("Save Image As...")

    action = menu.exec(image_label.mapToGlobal(pos))

    if action == copy_image_action:
        copy_output_image(parent, image_label)
    elif action == save_as_action:
        file_dialog = QFileDialog()
        save_path, _ = file_dialog.getSaveFileName(parent, "Save Image", os.path.basename(image_path), "Images (*.png *.jpg *.bmp)")
        if save_path:
            image_label.pixmap().save(save_path)

Display right-click menu on image labels.

def show_table_context_menu(pos, table_view, copy_action)
Expand source code
def show_table_context_menu(pos, table_view, copy_action):
    """
    Displays the context menu for the QTableView.
    Args:
        pos (QPoint): The position where the context menu is requested.
        table_view (QTableView): The table view for which the context menu is displayed.
        copy_action (function): The function to execute when "Copy" is selected.
    """
    menu = QMenu(table_view)
    copy_action_item = QAction("Copy", table_view)
    copy_action_item.triggered.connect(copy_action)
    menu.addAction(copy_action_item)
    menu.exec(table_view.mapToGlobal(pos))

Displays the context menu for the QTableView.

Args

pos : QPoint
The position where the context menu is requested.
table_view : QTableView
The table view for which the context menu is displayed.
copy_action : function
The function to execute when "Copy" is selected.