728x90

1. 목표

더보기

 

새 파일 메뉴를 눌렀을 때

  • 현재 문서가 수정되어 있다면 먼저 저장 여부를 묻고
  • 취소를 누르면 아무 일도 하지 않고
  • 계속 진행을 선택하면 에디터를 깨끗한 새 문서 상태로 초기화한다.

저장 기능은 이전 단계에서 구현한 Qt 스타일 파일 저장 함수와
(QFile, QIODevice, QDir, QTextStream 기반)인 save_file 을 재사용한다.


 

2. 전체 로직

더보기
# mainwindow.py
 
from PySide6.QtWidgets import QMainWindow, QFileDialog, QMessageBox
from PySide6.QtCore import QFile, QIODevice, QDir, QTextStream
 
from ui_form import Ui_MainWindow
 
 
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)
        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        self.current_file = ''

        self.ui.actionNew.triggered.connect(self.file_new)
        self.ui.actionSave.triggered.connect(self.save_file)

    def file_new(self):
        if not self.maybe_save():
            return

        self.ui.textEdit.clear()
        self.current_file = ''
        self.ui.textEdit.document().setModified(False)
        self.setWindowTitle('제목 없음 - 메모장')

    def maybe_save(self) -> bool:
        if not self.ui.textEdit.document().isModified():
            return True

        ret = QMessageBox.warning(
            self,
            '문서 저장',
            '문서가 수정되었습니다.\n변경 내용을 저장하시겠습니까',
            QMessageBox.StandardButton.Save
            | QMessageBox.StandardButton.Discard
            | QMessageBox.StandardButton.Cancel
        )

        if ret == QMessageBox.StandardButton.Save:
            return self.save_file()
        if ret == QMessageBox.StandardButton.Cancel:
            return False
        return True

    def save_file(self) -> bool:
        filename, _ = QFileDialog.getSaveFileName(
            self,
            '파일 저장',
            QDir.homePath(),
            'Text Files (*.txt);;All Files (*)'
        )

        if not filename:
            return False

        file = QFile(filename)
        if not file.open(QIODevice.WriteOnly | QIODevice.Text):
            QMessageBox.critical(self, '오류', f'파일을 열 수 없습니다:\n{file.errorString()}')
            return False

        stream = QTextStream(file)
        stream << self.ui.textEdit.toPlainText()
        file.close()

        self.current_file = filename

        QMessageBox.information(self, '저장 성공', f'파일이 저장되었습니다.\n\n{filename}')
        return True
# main.py

import sys
from PySide6.QtWidgets import QApplication

from mainwindow import MainWindow

if __name__ == "__main__":
    app = QApplication(sys.argv)
    w = MainWindow()
    w.show()
    sys.exit(app.exec())

 

3. 필요한 import + 주석 설명

더보기

동일

 

4. 메뉴 액션과 슬롯 연결하기 (actionNew ↔ file_new)

더보기
class MainWindow(QMainWindow):
    def __init__(self, parent=None):
        super().__init__(parent)

        self.ui = Ui_MainWindow()
        self.ui.setupUi(self)

        # 현재 열려 있는 파일 경로를 기억하기 위한 멤버 변수
        # 새 문서 상태에서는 빈 문자열로 둔다
        self.current_file = ''

        # 9.3 에서 이미 구현한 파일 저장 기능과 연결
        # 새 파일을 만들 때 수정 내용 저장 여부를 묻기 위해 save_file 을 재사용한다
        self.ui.actionSave.triggered.connect(self.save_file)
        
        # 9.4 새 파일 열기 기능
        # 메뉴의 새 파일 액션을 file_new 메서드에 연결한다
        self.ui.actionNew.triggered.connect(self.file_new)

여기서 중요한 점은
새 파일 동작이 단독으로 존재하는 것이 아니라
반드시 save_file, maybe_save 와 함께 돌아간다는 점이다.


 

5. 실제 새 파일 열기 기능 구현 함수

더보기

5.1 새 파일 처리 함수 file_new

    def file_new(self):
        # 새 파일을 만들기 전에
        # 현재 문서에 저장되지 않은 변경 사항이 있다면
        # 먼저 저장 여부를 물어야 한다
        if not self.maybe_save():
            # maybe_save 가 False 를 반환하면
            # 사용자가 저장 취소를 선택한 것이므로
            # 새 파일 생성도 취소한다
            return

        # 여기까지 왔다면
        #   1) 변경 사항이 없었거나
        #   2) 변경 사항을 이미 저장했거나
        #   3) 변경 사항을 버리겠다고 선택한 상태이다

        # 에디터 내용을 지워서 완전히 새 문서 상태로 만든다
        self.ui.textEdit.clear()

        # 현재 파일 경로도 초기화한다
        # 빈 문자열은 아직 디스크에 저장되지 않은 새 문서라는 의미로 사용한다
        self.current_file = ''

        # 수정 상태 플래그를 초기화한다
        self.ui.textEdit.document().setModified(False)

        # 창 제목을 새 문서임을 알 수 있도록 변경한다
        self.setWindowTitle('제목 없음 - 메모장')

 

5.2 저장 여부를 묻는 함수 maybe_save

    def maybe_save(self) -> bool:
        # 문서가 수정되지 않았다면
        # 바로 계속 진행해도 되므로 True 를 반환한다
        if not self.ui.textEdit.document().isModified():
            return True

        # 여기까지 왔다는 것은
        # textEdit 내용이 마지막 저장 이후로 변경되었다는 의미이다
        # 사용자에게 변경 내용을 저장할지 물어본다
        ret = QMessageBox.warning(
            self,
            '문서 저장',
            '문서가 수정되었습니다.\n변경 내용을 저장하시겠습니까',
            QMessageBox.StandardButton.Save
            | QMessageBox.StandardButton.Discard
            | QMessageBox.StandardButton.Cancel
        )

        # 저장 버튼을 눌렀다면
        # save_file 을 호출해서 실제로 저장을 시도한다
        # save_file 이 True 를 반환하면 계속 진행 가능
        if ret == QMessageBox.StandardButton.Save:
            return self.save_file()

        # 취소 버튼을 눌렀다면
        # 호출한 쪽에서 작업을 중단해야 하므로 False 를 반환한다
        if ret == QMessageBox.StandardButton.Cancel:
            return False

        # 나머지 경우는 버리기 버튼을 누른 경우이므로
        # 저장하지 않고 계속 진행해도 된다
        return True


6. 실행

더보기

 

 

 


 

6. 실행 테스트

 

7.학습 주요 포인트

더보기

1 새 파일 기능의 핵심 구조

  • 단순히 textEdit 를 clear 하는 것이 전부가 아니다.
  • 반드시 다음 순서를 따라야 안전하다.
    1 수정 여부 확인 maybe_save
    2 사용자가 저장 또는 버리기 를 선택하면 새 문서로 초기화
    3 취소를 선택하면 아무 것도 하지 않음

 

2 수정 여부 검사와 document modified 플래그

  • QTextDocument 의 isModified 를 사용하면
    에디터가 마지막 저장 이후로 변경되었는지 쉽게 확인할 수 있다.
  • 새 문서를 만들거나 저장을 완료한 뒤에는
    setModified False 로 상태를 초기화해야 한다.

 

3 maybe_save 의 반환값 의미

  • True 계속 진행해도 괜찮다.
    새 파일 만들기, 프로그램 종료 등 다음 작업을 수행할 수 있다.
  • False 사용자가 취소를 눌렀으므로
    호출한 쪽에서는 작업을 중단해야 한다.

 

4 Qt 파일 I O 와의 연계

  • 새 파일 기능 자체는 디스크 I O 를 직접 사용하지 않지만
    내부적으로 save_file 을 호출할 수 있으므로
    저장 로직은 QFile, QIODevice, QTextStream 으로 구현되어 있어야
    Qt 의 다른 부분과 자연스럽게 연결된다.

 

5 current_file 멤버의 역할

  • 현재 열려 있는 파일의 경로를 저장하기 위해 사용한다.
  • 새 파일일 경우 빈 문자열로 두어
    아직 디스크에 저장되지 않은 문서라는 것을 표시할 수 있다.
  • 이후 단계에서
    이미 파일 이름이 있는 경우에는 바로 덮어쓰기 저장을 하는 등
    더 진화된 저장 로직을 만들 수 있다.

 

 

단계별 완성 파일