├── .gitignore ├── CMakeLists.txt ├── COPYING ├── Modules └── FindQScintilla.cmake ├── README.txt └── src ├── cli.cxx ├── main.cxx ├── qt ├── editor_widget.cxx ├── editor_widget.hxx ├── goto_line_dialog.cxx ├── goto_line_dialog.hxx ├── line_edit.hxx ├── main_window.cxx ├── main_window.hxx ├── pinned_script_list.cxx ├── pinned_script_list.hxx ├── savediscard_dialog.cxx ├── savediscard_dialog.hxx ├── script_archive.cxx ├── script_archive.hxx ├── search_bar.cxx └── search_bar.hxx ├── ruby_data.cxx └── ruby_data.hxx /.gitignore: -------------------------------------------------------------------------------- 1 | src/qt/moc_* 2 | Makefile 3 | build/ 4 | *_parameters 5 | -------------------------------------------------------------------------------- /CMakeLists.txt: -------------------------------------------------------------------------------- 1 | cmake_minimum_required(VERSION 2.8) 2 | project(rgss_script_editor CXX C) 3 | 4 | set(EXECUTABLE_OUTPUT_PATH "${CMAKE_BINARY_DIR}/bin") 5 | set(LIBRARY_OUTPUT_PATH "${CMAKE_BINARY_DIR}/lib") 6 | 7 | list(APPEND CMAKE_MODULE_PATH "${CMAKE_CURRENT_SOURCE_DIR}/Modules") 8 | 9 | find_package(Qt5 REQUIRED Core Widgets Gui) 10 | find_package(QScintilla REQUIRED) 11 | find_package(ZLIB REQUIRED) 12 | 13 | include_directories( 14 | "${CMAKE_CURRENT_SOURCE_DIR}/src" 15 | ${RUBY_INCLUDE_DIRS} ${QT_INCLUDE_DIR} 16 | ${QSCINTILLA_INCLUDE_DIRS} ${ZLIB_INCLUDE_DIRS}) 17 | 18 | file(GLOB QT_HEADERS "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/*.hxx") 19 | qt5_wrap_cpp(QT_MOC_OUTPUT ${QT_HEADERS}) 20 | 21 | set(EDITOR_SRCS 22 | "${CMAKE_CURRENT_SOURCE_DIR}/src/main.cxx" 23 | "${CMAKE_CURRENT_SOURCE_DIR}/src/ruby_data.cxx" 24 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/" 25 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/editor_widget.cxx" 26 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/main_window.cxx" 27 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/savediscard_dialog.cxx" 28 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/script_archive.cxx" 29 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/pinned_script_list.cxx" 30 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/search_bar.cxx" 31 | "${CMAKE_CURRENT_SOURCE_DIR}/src/qt/goto_line_dialog.cxx" 32 | ) 33 | set(CLI_SRCS 34 | "${CMAKE_CURRENT_SOURCE_DIR}/src/ruby_data.cxx" 35 | "${CMAKE_CURRENT_SOURCE_DIR}/src/cli.cxx" 36 | ) 37 | 38 | list(APPEND EDITOR_SRCS "${QT_MOC_OUTPUT}") 39 | 40 | add_executable("${PROJECT_NAME}" ${EDITOR_SRCS}) 41 | add_executable("${PROJECT_NAME}_cli" ${CLI_SRCS}) 42 | 43 | target_link_libraries("${PROJECT_NAME}" 44 | Qt5::Widgets 45 | ${QSCINTILLA_LIBRARY} ${ZLIB_LIBRARIES}) 46 | target_link_libraries("${PROJECT_NAME}_cli" 47 | Qt5::Widgets 48 | ${QSCINTILLA_LIBRARY} ${ZLIB_LIBRARIES}) 49 | -------------------------------------------------------------------------------- /COPYING: -------------------------------------------------------------------------------- 1 | The MIT License (MIT) 2 | 3 | Permission is hereby granted, free of charge, to any person obtaining a copy 4 | of this software and associated documentation files (the "Software"), to deal 5 | in the Software without restriction, including without limitation the rights 6 | to use, copy, modify, merge, publish, distribute, sublicense, and/or sell 7 | copies of the Software, and to permit persons to whom the Software is 8 | furnished to do so, subject to the following conditions: 9 | 10 | The above copyright notice and this permission notice shall be included in 11 | all copies or substantial portions of the Software. 12 | 13 | THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR 14 | IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, 15 | FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE 16 | AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER 17 | LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, 18 | OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN 19 | THE SOFTWARE. 20 | -------------------------------------------------------------------------------- /Modules/FindQScintilla.cmake: -------------------------------------------------------------------------------- 1 | # QScintilla_FOUND 2 | # QSCINTILLA_INCLUDE_DIRS 3 | # QSCINTILLA_LIBRARY 4 | 5 | if(Qt5Core_FOUND) 6 | find_path(QSCINTILLA_INCLUDE_INTERNAL 7 | NAMES Qsci/qsciapis.h 8 | PATHS ${Qt5Core_INCLUDE_DIRS}) 9 | 10 | find_library(QSCINTILLA_LIBRARY 11 | NAMES 12 | qscintilla2_qt5 13 | qt5scintilla2 14 | qscintilla2-qt5) 15 | 16 | if(QSCINTILLA_INCLUDE_INTERNAL AND QSCINTILLA_LIBRARY) 17 | set(QScintilla_FOUND TRUE) 18 | set(QSCINTILLA_INCLUDE_DIRS "${QSCINTILLA_INCLUDE_INTERNAL}/Qsci") 19 | message(STATUS "QScintilla found: ${QSCINTILLA_LIBRARY}") 20 | else() 21 | set(QScintilla_FOUND FALSE) 22 | endif() 23 | else() 24 | message(STATUS "Qt5Core not found. Find it before this script") 25 | set(QScintilla_FOUND FALSE) 26 | endif() 27 | 28 | if(NOT QScintilla_FOUND AND QScintilla_FIND_REQUIRED) 29 | message(FATAL_ERROR "Cannot find QScintilla library") 30 | endif() 31 | -------------------------------------------------------------------------------- /README.txt: -------------------------------------------------------------------------------- 1 | An editor for RPGMaker script archives. 2 | 3 | Dependencies: 4 | Qt5 5 | QScintilla2-Qt5 6 | CMake (build only) 7 | 8 | Licensed under the MIT license (see COPYING). 9 | -------------------------------------------------------------------------------- /src/cli.cxx: -------------------------------------------------------------------------------- 1 | 2 | #include "ruby_data.hxx" 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | 9 | #include 10 | 11 | using namespace std; 12 | 13 | int saveScripts(QString const& file, const ScriptList &scripts) { 14 | /* Determine marshal format */ 15 | QFileInfo finfo(file); 16 | QString fileExt = finfo.suffix(); 17 | Script::Format format; 18 | 19 | if (fileExt == "rxdata") 20 | format = Script::XP; 21 | else if (fileExt == "rvdata") 22 | format = Script::XP; 23 | else if (fileExt == "rvdata2") 24 | format = Script::VXAce; 25 | else { 26 | cerr << "Unrecognized file extension: " << fileExt.toUtf8().constData() << endl; 27 | return 1; 28 | } 29 | 30 | QFile archiveFile(file); 31 | if (!archiveFile.open(QFile::WriteOnly)) { 32 | cerr << "Cannot open for writing: " << file.toUtf8().constData() << endl; 33 | return 1; 34 | } 35 | 36 | try { 37 | writeScripts(scripts, archiveFile, format); 38 | archiveFile.close(); 39 | } 40 | catch (const QByteArray &) { 41 | cerr << "Cannot save: " << file.toUtf8().constData() << endl; 42 | return 1; 43 | } 44 | 45 | return 0; 46 | } 47 | 48 | int import(QString src_folder, ScriptList &scripts) { 49 | if (src_folder.isEmpty()) 50 | return 1; 51 | 52 | /* Open index */ 53 | QFile indFile(src_folder + "/index"); 54 | if (!indFile.open(QFile::ReadOnly)) { 55 | cerr << "Cannot open index file" << endl; 56 | return 1; 57 | } 58 | 59 | QTextStream indStream(&indFile); 60 | int scIdx = 0; 61 | 62 | while (!indStream.atEnd()) 63 | { 64 | QString scName = indStream.readLine(); 65 | 66 | QString scFilename = QString("%1").arg(scIdx, 3, 10, QLatin1Char('0')); 67 | QFile scFile(src_folder + "/" + scFilename); 68 | 69 | if (!scFile.open(QFile::ReadOnly)) { 70 | cerr << QString("Cannot open script \"%1\" (%2)").arg(scName, scFilename).toUtf8().constData() << endl; 71 | return 1; 72 | } 73 | 74 | QByteArray scData = scFile.readAll(); 75 | scFile.close(); 76 | 77 | Script script; 78 | script.magic = 0; 79 | script.name = scName; 80 | script.data = scData; 81 | 82 | scripts.append(script); 83 | 84 | ++scIdx; 85 | } 86 | 87 | return 0; 88 | } 89 | 90 | int main(int argc, char* argv[]) { 91 | if (argc < 3) { 92 | puts("Wrong number of arguments (cli )"); 93 | return 1; 94 | } 95 | const QString src_folder = QString::fromLocal8Bit(argv[1]); 96 | const QString out_file = QString::fromLocal8Bit(argv[2]); 97 | ScriptList scripts; 98 | if (import(src_folder, scripts)) return 1; 99 | if (saveScripts(out_file, scripts)) return 1; 100 | return 0; 101 | } 102 | -------------------------------------------------------------------------------- /src/main.cxx: -------------------------------------------------------------------------------- 1 | #include "ruby_data.hxx" 2 | #include "qt/main_window.hxx" 3 | 4 | #include 5 | #include 6 | 7 | int main(int argc, char* argv[]) { 8 | QApplication app(argc, argv); 9 | 10 | QString initial_path; 11 | if(argc >= 2) { 12 | initial_path = QString::fromUtf8(argv[1]); 13 | } 14 | 15 | RGSS_MainWindow main(initial_path); 16 | main.show(); 17 | 18 | main.activateWindow(); 19 | main.raise(); 20 | 21 | return app.exec(); 22 | } 23 | -------------------------------------------------------------------------------- /src/qt/editor_widget.cxx: -------------------------------------------------------------------------------- 1 | #include "editor_widget.hxx" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | 10 | #include 11 | 12 | #include 13 | 14 | EditorWidget::EditorWidget(QWidget *parent) 15 | : QsciScintilla(parent) 16 | { 17 | setAcceptDrops(true); 18 | 19 | // Set cursor color to text color 20 | const QColor &textColor = palette().text().color(); 21 | setCaretForegroundColor(textColor); 22 | 23 | /* Disable default Ctrl+L command so we can use it for "goto line" */ 24 | standardCommands()->boundTo(Qt::CTRL + Qt::Key_L)->setKey(0); 25 | } 26 | 27 | void EditorWidget::centreLine() 28 | { 29 | standardCommands()->find(QsciCommand::VerticalCentreCaret)->execute(); 30 | } 31 | 32 | void EditorWidget::dragEnterEvent(QDragEnterEvent *e) 33 | { 34 | if (!e->mimeData()->hasUrls()) { 35 | e->ignore(); 36 | return; 37 | } 38 | 39 | QList urls = e->mimeData()->urls(); 40 | 41 | if (urls.isEmpty()) { 42 | e->ignore(); 43 | return; 44 | } 45 | 46 | QString filename = urls.first().toLocalFile(); 47 | QString suffix = QFileInfo(filename).suffix(); 48 | 49 | if (suffix != "rxdata" && suffix != "rvdata" && suffix != "rvdata2") { 50 | e->ignore(); 51 | return; 52 | } 53 | 54 | e->acceptProposedAction(); 55 | } 56 | 57 | void EditorWidget::dragMoveEvent(QDragMoveEvent *e) 58 | { 59 | e->acceptProposedAction(); 60 | } 61 | 62 | void EditorWidget::dropEvent(QDropEvent *e) 63 | { 64 | QString filename = e->mimeData()->urls().first().toLocalFile(); 65 | 66 | archiveDropped(filename); 67 | } 68 | -------------------------------------------------------------------------------- /src/qt/editor_widget.hxx: -------------------------------------------------------------------------------- 1 | #ifndef EDITOR_WIDGET_HXX 2 | #define EDITOR_WIDGET_HXX 3 | 4 | #include 5 | 6 | class EditorWidget : public QsciScintilla 7 | { 8 | Q_OBJECT 9 | public: 10 | EditorWidget(QWidget *parent = 0); 11 | 12 | void centreLine(); 13 | 14 | signals: 15 | void archiveDropped(const QString &filename); 16 | 17 | private: 18 | void dragEnterEvent(QDragEnterEvent *); 19 | void dragMoveEvent(QDragMoveEvent *e); 20 | void dropEvent(QDropEvent *e); 21 | }; 22 | 23 | #endif // EDITOR_WIDGET_HXX 24 | -------------------------------------------------------------------------------- /src/qt/goto_line_dialog.cxx: -------------------------------------------------------------------------------- 1 | #include "goto_line_dialog.hxx" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | GotoLineDialog::GotoLineDialog(QWidget *parent) 8 | : QDialog(parent) 9 | { 10 | /* The window will be too small for a title */ 11 | setWindowTitle(QString(" ")); 12 | 13 | edit = new LineEdit(this); 14 | edit->setValidator(new QIntValidator(this)); 15 | edit->setFixedWidth(50); 16 | 17 | QBoxLayout *box = new QHBoxLayout(); 18 | box->addWidget(new QLabel(tr("Line:"))); 19 | box->addWidget(edit); 20 | 21 | setLayout(box); 22 | 23 | connect(edit, SIGNAL(returnPressed()), SLOT(accept())); 24 | connect(edit, SIGNAL(escapePressed()), SLOT(reject())); 25 | 26 | resize(minimumSize()); 27 | } 28 | 29 | int GotoLineDialog::getLine() 30 | { 31 | return edit->text().toInt(); 32 | } 33 | -------------------------------------------------------------------------------- /src/qt/goto_line_dialog.hxx: -------------------------------------------------------------------------------- 1 | #ifndef GOTO_LINE_DIALOG_HXX 2 | #define GOTO_LINE_DIALOG_HXX 3 | 4 | #include 5 | 6 | #include "line_edit.hxx" 7 | 8 | class QLineEdit; 9 | 10 | class GotoLineDialog : public QDialog 11 | { 12 | public: 13 | GotoLineDialog(QWidget *parent = 0); 14 | 15 | int getLine(); 16 | 17 | private: 18 | LineEdit *edit; 19 | }; 20 | 21 | #endif // GOTO_LINE_DIALOG_HXX 22 | -------------------------------------------------------------------------------- /src/qt/line_edit.hxx: -------------------------------------------------------------------------------- 1 | #ifndef LINE_EDIT_HXX 2 | #define LINE_EDIT_HXX 3 | 4 | #include 5 | #include 6 | 7 | class LineEdit : public QLineEdit 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | LineEdit(QWidget *parent = 0) 13 | : QLineEdit(parent) 14 | {} 15 | 16 | signals: 17 | void escapePressed(); 18 | 19 | private: 20 | void keyPressEvent(QKeyEvent *ke) 21 | { 22 | if (ke->key() == Qt::Key_Escape) 23 | { 24 | escapePressed(); 25 | ke->accept(); 26 | return; 27 | } 28 | 29 | QLineEdit::keyPressEvent(ke); 30 | } 31 | }; 32 | 33 | #endif // LINE_EDIT_HXX 34 | -------------------------------------------------------------------------------- /src/qt/main_window.cxx: -------------------------------------------------------------------------------- 1 | #include "main_window.hxx" 2 | 3 | #include "savediscard_dialog.hxx" 4 | #include "editor_widget.hxx" 5 | #include "pinned_script_list.hxx" 6 | #include "goto_line_dialog.hxx" 7 | 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | #include 15 | #include 16 | #include 17 | #include 18 | #include 19 | #include 20 | #include 21 | 22 | #include 23 | 24 | #include 25 | #include 26 | 27 | #define SETTINGS_ORG "rgss_script_editor" 28 | #define SETTINGS_APP "rgss_script_editor" 29 | 30 | RGSS_MainWindow::RGSS_MainWindow(const QString &path_to_load, 31 | QWidget* const parent, Qt::WindowFlags const flags) 32 | : QMainWindow(parent, flags) 33 | , data_modified_(false) 34 | , pinned_model_(this) 35 | { 36 | /* Read settings */ 37 | QSettings settings(SETTINGS_ORG, SETTINGS_APP); 38 | QSize window_size = (settings.value("window_size", QSize(800, 600)).toSize()); 39 | QString last_open_path = settings.value("open_path").toString(); 40 | last_valid_folder = settings.value("last_valid_folder", QDir::homePath()).toString(); 41 | last_valid_folder_impexp = 42 | settings.value("last_valid_folder_impexp", QDir::homePath()).toString(); 43 | archive_.setScriptNumsVisible(settings.value("show_script_indices", false).toBool()); 44 | 45 | resize(window_size); 46 | 47 | /* Setup UI */ 48 | QMenuBar* const menu_bar = new QMenuBar(this); 49 | { 50 | QMenu* const file = new QMenu(tr("File"), menu_bar); 51 | 52 | QAction* const open = new QAction(tr("Open"), menu_bar); 53 | open->setShortcut(QKeySequence(QKeySequence::Open)); 54 | connect(open, SIGNAL(triggered()), SLOT(onOpenArchive())); 55 | file->addAction(open); 56 | 57 | QAction* const save = new QAction(tr("Save"), menu_bar); 58 | save->setShortcut(QKeySequence(QKeySequence::Save)); 59 | connect(save, SIGNAL(triggered()), SLOT(onSaveArchive())); 60 | file->addAction(save); 61 | 62 | QAction* const save_as = new QAction(tr("Save As"), menu_bar); 63 | save_as->setShortcut(QKeySequence(QKeySequence::SaveAs)); 64 | connect(save_as, SIGNAL(triggered()), SLOT(onSaveArchiveAs())); 65 | file->addAction(save_as); 66 | 67 | file->addSeparator(); 68 | 69 | QAction* const imp_scripts = new QAction(tr("Import Scripts"), menu_bar); 70 | connect(imp_scripts, SIGNAL(triggered()), SLOT(onImportScripts())); 71 | file->addAction(imp_scripts); 72 | 73 | QAction* const exp_scripts = new QAction(tr("Export Scripts"), menu_bar); 74 | connect(exp_scripts, SIGNAL(triggered()), SLOT(onExportScripts())); 75 | file->addAction(exp_scripts); 76 | 77 | file->addSeparator(); 78 | 79 | QAction* const close = new QAction(tr("Close"), menu_bar); 80 | close->setShortcut(QKeySequence(QKeySequence::Close)); 81 | connect(close, SIGNAL(triggered()), SLOT(onCloseArchive())); 82 | file->addAction(close); 83 | 84 | edit_menu_.setTitle(tr("Edit")); 85 | 86 | QAction* const insert = new QAction(tr("Insert"), menu_bar); 87 | connect(insert, SIGNAL(triggered()), SLOT(onInsertScript())); 88 | edit_menu_.addAction(insert); 89 | 90 | delete_action_ = new QAction(tr("Delete"), menu_bar); 91 | connect(delete_action_, SIGNAL(triggered()), SLOT(onDeleteScript())); 92 | delete_action_->setShortcut(QKeySequence(QKeySequence::Delete)); 93 | edit_menu_.addAction(delete_action_); 94 | 95 | pin_action_ = new QAction(tr("Pin"), menu_bar); 96 | connect(pin_action_, SIGNAL(triggered()), SLOT(onPinScript())); 97 | edit_menu_.addAction(pin_action_); 98 | 99 | edit_menu_.addSeparator(); 100 | 101 | QAction *find = new QAction(tr("Find"), menu_bar); 102 | find->setShortcut(QKeySequence(QKeySequence::Find)); 103 | connect(find, SIGNAL(triggered()), SLOT(onShowSearchBar())); 104 | edit_menu_.addAction(find); 105 | 106 | QAction *goline = new QAction(tr("Go to line"), menu_bar); 107 | goline->setShortcut(QKeySequence(Qt::CTRL + Qt::Key_L)); 108 | connect(goline, SIGNAL(triggered()), SLOT(onGotoLine())); 109 | edit_menu_.addAction(goline); 110 | 111 | QMenu *view = new QMenu(this); 112 | view->setTitle(tr("View")); 113 | QAction *scriptnum = new QAction(tr("Show script indices"), menu_bar); 114 | scriptnum->setCheckable(true); 115 | scriptnum->setChecked(archive_.scriptNumsVisible()); 116 | connect(scriptnum, SIGNAL(toggled(bool)), &archive_, SLOT(setScriptNumsVisible(bool))); 117 | view->addAction(scriptnum); 118 | 119 | menu_bar->addMenu(file); 120 | menu_bar->addMenu(&edit_menu_); 121 | menu_bar->addMenu(view); 122 | } 123 | setMenuBar(menu_bar); 124 | 125 | splitter_.setOrientation(Qt::Horizontal); 126 | setCentralWidget(&splitter_); 127 | 128 | QMenu *pinned_menu = new QMenu(this); 129 | pinned_menu->addAction(tr("Unpin"), this, SLOT(onUnpinScript())); 130 | pinned_list_.setContextMenu(pinned_menu); 131 | 132 | pinned_model_.setSourceModel(&archive_); 133 | pinned_list_.setModel(&pinned_model_); 134 | 135 | script_list_.setModel(&archive_); 136 | script_list_.setContextMenu(&edit_menu_); 137 | 138 | QBoxLayout *left_vbox = new QVBoxLayout(&left_side_); 139 | left_vbox->addWidget(&pinned_list_); 140 | left_vbox->addWidget(&script_list_); 141 | left_vbox->addWidget(&script_name_editor_); 142 | left_vbox->setStretchFactor(&pinned_list_, 30); 143 | left_vbox->setStretchFactor(&script_list_, 70); 144 | pinned_list_.setVisible(false); 145 | 146 | editor_stack.addWidget(&dummy_editor); 147 | 148 | QWidget *right_side = new QWidget(this); 149 | QBoxLayout *right_vbox = new QVBoxLayout(right_side); 150 | right_vbox->addWidget(&editor_stack, 100); 151 | right_vbox->addWidget(&search_bar_, 1); 152 | 153 | splitter_.addWidget(&left_side_); 154 | splitter_.addWidget(right_side); 155 | 156 | search_bar_.setVisible(false); 157 | connect(&search_bar_, SIGNAL(hidePressed()), SLOT(onSearchBarHidePressed())); 158 | connect(&search_bar_, SIGNAL(searchComitted(QString)), SLOT(onSearchComitted(QString))); 159 | connect(&search_bar_, SIGNAL(searchNext()), SLOT(onSearchNext())); 160 | 161 | /* Only the editor widget should expand on resize */ 162 | splitter_.setStretchFactor(0, 0); 163 | splitter_.setStretchFactor(1, 1); 164 | 165 | const int left_size_width = 200; 166 | QList sizes; 167 | sizes.push_back(left_size_width); 168 | sizes.push_back(window_size.width() - left_size_width); 169 | splitter_.setSizes(sizes); 170 | 171 | setupEditor(dummy_editor); 172 | connect(&dummy_editor, SIGNAL(archiveDropped(QString)), SLOT(onArchiveDropped(QString))); 173 | 174 | connect(pinned_list_.selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(onPinnedIndexChange(QModelIndex,QModelIndex))); 175 | 176 | connect(script_list_.selectionModel(), SIGNAL(currentChanged(QModelIndex,QModelIndex)), SLOT(onScriptIndexChange(QModelIndex,QModelIndex))); 177 | connect(&script_name_editor_, SIGNAL(textEdited(QString)), SLOT(onScriptNameEdited(QString))); 178 | 179 | connect(&pinned_list_, SIGNAL(doubleClicked(QModelIndex)), SLOT(onUnpinScript())); 180 | connect(&script_list_, SIGNAL(doubleClicked(QModelIndex)), SLOT(onPinScript())); 181 | 182 | connect(&archive_, SIGNAL(scriptCountChanged(int)), SLOT(onScriptCountChanged(int))); 183 | 184 | enableEditing(false); 185 | 186 | if (!path_to_load.isEmpty()) 187 | loadScriptArchive(path_to_load); 188 | else if (!last_open_path.isEmpty()) 189 | loadScriptArchive(last_open_path, false); 190 | 191 | updateWindowTitle(); 192 | } 193 | 194 | void RGSS_MainWindow::setupEditor(QsciScintilla &editor) 195 | { 196 | // other setting 197 | editor.setUtf8(true); 198 | editor.setEolMode(QsciScintilla::EolWindows); 199 | 200 | // indent 201 | editor.setIndentationWidth(2); 202 | editor.setAutoIndent(true); 203 | 204 | // lexer and font 205 | QFont font; 206 | font.setStyleHint(QFont::Monospace); 207 | font.setFamily(font.defaultFamily()); 208 | editor.setFont(font); 209 | editor.setMarginsFont(font); 210 | QsciLexer* lexer = new QsciLexerRuby(&editor); 211 | lexer->setFont(font, QsciLexerRuby::Comment); 212 | lexer->setDefaultFont(font); 213 | editor.setLexer(lexer); 214 | 215 | // line number 216 | QFontMetrics fontmetrics(font); 217 | editor.setMarginWidth(0, fontmetrics.horizontalAdvance("00000") + 6); 218 | editor.setMarginLineNumbers(0, true); 219 | 220 | // auto complete 221 | editor.setAutoCompletionThreshold(3); 222 | editor.setAutoCompletionSource(QsciScintilla::AcsAll); 223 | } 224 | 225 | EditorWidget *RGSS_MainWindow::getEditorForScript(Script *script) 226 | { 227 | /* If we already have an editor associated 228 | * with this script, just return that */ 229 | if (editor_hash.contains(script)) 230 | return editor_hash.value(script); 231 | 232 | /* Otherwise, create (or recycle) a new one */ 233 | EditorWidget *editor; 234 | 235 | /* Check recycled list first */ 236 | if (!recycled_editors.isEmpty()) { 237 | editor = recycled_editors.back(); 238 | recycled_editors.pop_back(); 239 | } else { 240 | editor = new EditorWidget(); 241 | setupEditor(*editor); 242 | editor_stack.addWidget(editor); 243 | connect(editor, SIGNAL(archiveDropped(QString)), SLOT(onArchiveDropped(QString))); 244 | } 245 | 246 | /* Associate editor with script */ 247 | editor->setText(script->data); 248 | editor_hash.insert(script, editor); 249 | 250 | /* Listen for text changes */ 251 | connect(editor, SIGNAL(textChanged()), SLOT(onScriptEditorModified())); 252 | 253 | return editor; 254 | } 255 | 256 | EditorWidget *RGSS_MainWindow::getCurrentEditor() 257 | { 258 | return static_cast(editor_stack.currentWidget()); 259 | } 260 | 261 | QModelIndex RGSS_MainWindow::getCurrentIndex() 262 | { 263 | return script_list_.selectionModel()->currentIndex(); 264 | } 265 | 266 | void RGSS_MainWindow::storeChangedScripts() 267 | { 268 | QHash::const_iterator iter; 269 | for (iter = editor_hash.constBegin(); iter != editor_hash.constEnd(); ++iter) { 270 | QsciScintilla *editor = iter.value(); 271 | 272 | if (!editor->isModified()) 273 | continue; 274 | 275 | iter.key()->data = editor->text(); 276 | editor->setModified(false); 277 | } 278 | } 279 | 280 | bool RGSS_MainWindow::verifySaveDiscard(const QString &actionTitle) 281 | { 282 | if (data_modified_) { 283 | SaveDiscardDialog dialog(this); 284 | dialog.setWindowTitle(actionTitle); 285 | 286 | switch (dialog.exec()) { 287 | default: 288 | case SaveDiscardDialog::Cancel : 289 | return false; 290 | 291 | case SaveDiscardDialog::Save : 292 | return saveScriptArchiveAs(open_path); 293 | 294 | case SaveDiscardDialog::Discard : 295 | break; 296 | } 297 | } 298 | 299 | return true; 300 | } 301 | 302 | void RGSS_MainWindow::onScriptEditorModified() 303 | { 304 | setDataModified(true); 305 | } 306 | 307 | void RGSS_MainWindow::setDataModified(bool m) 308 | { 309 | if (data_modified_ == m) 310 | return; 311 | 312 | data_modified_ = m; 313 | 314 | updateWindowTitle(); 315 | } 316 | 317 | void RGSS_MainWindow::onArchiveDropped(const QString &filename) 318 | { 319 | if (!verifySaveDiscard(tr("Open Archive"))) 320 | return; 321 | 322 | if (archive_opened) 323 | closeScriptArchive(); 324 | 325 | loadScriptArchive(filename); 326 | 327 | updateWindowTitle(); 328 | } 329 | 330 | void RGSS_MainWindow::onShowSearchBar() 331 | { 332 | EditorWidget *ed = static_cast(editor_stack.currentWidget()); 333 | 334 | QString selected = ed->selectedText(); 335 | 336 | if (!selected.isEmpty()) 337 | search_bar_.setEditText(selected); 338 | 339 | search_bar_.onShow(); 340 | } 341 | 342 | void RGSS_MainWindow::onSearchBarHidePressed() 343 | { 344 | search_bar_.hide(); 345 | editor_stack.currentWidget()->setFocus(); 346 | } 347 | 348 | void RGSS_MainWindow::onSearchComitted(const QString &text) 349 | { 350 | EditorWidget *cur = getCurrentEditor(); 351 | 352 | int line, pos, noop; 353 | cur->getSelection(&line, &pos, &noop, &noop); 354 | 355 | bool found = cur->findFirst(text, false, true, false, true, true, line, pos); 356 | 357 | if (!found) 358 | { 359 | search_bar_.setNotFoundFlag(); 360 | return; 361 | } 362 | 363 | cur->centreLine(); 364 | } 365 | 366 | void RGSS_MainWindow::onSearchNext() 367 | { 368 | if (getCurrentEditor()->findNext()) 369 | getCurrentEditor()->centreLine(); 370 | } 371 | 372 | void RGSS_MainWindow::onGotoLine() 373 | { 374 | EditorWidget *cur = getCurrentEditor(); 375 | 376 | GotoLineDialog dialog(this); 377 | 378 | if (dialog.exec() != QDialog::Accepted) 379 | return; 380 | 381 | int line = dialog.getLine()-1; 382 | 383 | if (line < 0 || line > cur->lines()) 384 | return; 385 | 386 | cur->setCursorPosition(line, 0); 387 | cur->centreLine(); 388 | cur->setFocus(); 389 | } 390 | 391 | void RGSS_MainWindow::onInsertScript() 392 | { 393 | int row = getCurrentIndex().row(); 394 | 395 | archive_.insertRow(row); 396 | script_list_.setCurrentIndex(archive_.index(row)); 397 | 398 | setDataModified(true); 399 | pinned_model_.invalidate(); 400 | } 401 | 402 | void RGSS_MainWindow::onDeleteScript() 403 | { 404 | QModelIndex current_index = getCurrentIndex(); 405 | 406 | if (!current_index.isValid()) 407 | return; 408 | 409 | pinned_list_.setCurrentIndex(QModelIndex()); 410 | 411 | int row = current_index.row(); 412 | 413 | Script *script = archive_.indexToScript(current_index); 414 | EditorWidget *editor = editor_hash.value(script); 415 | editor_hash.remove(script); 416 | recycled_editors.append(editor); 417 | 418 | archive_.removeRow(row); 419 | 420 | setDataModified(true); 421 | pinned_model_.invalidate(); 422 | } 423 | 424 | void RGSS_MainWindow::onPinScript() 425 | { 426 | QModelIndex current_index = getCurrentIndex(); 427 | 428 | if (!current_index.isValid()) 429 | return; 430 | 431 | Script *script = archive_.indexToScript(current_index); 432 | pinned_model_.addScript(*script); 433 | pinned_model_.invalidate(); 434 | 435 | pinned_list_.setVisible(true); 436 | pinned_list_.setCurrentIndex(pinned_model_.mapFromSource(current_index)); 437 | } 438 | 439 | void RGSS_MainWindow::onUnpinScript() 440 | { 441 | QModelIndex pinned_index = pinned_list_.selectionModel()->currentIndex(); 442 | 443 | if (!pinned_index.isValid()) 444 | return; 445 | 446 | Script *script = archive_.indexToScript(pinned_model_.mapToSource(pinned_index)); 447 | pinned_model_.removeScript(*script); 448 | 449 | pinned_model_.invalidate(); 450 | pinned_list_.setVisible(!pinned_model_.isEmpty()); 451 | } 452 | 453 | void RGSS_MainWindow::enableEditing(bool v) { 454 | script_name_editor_.setEnabled(v); 455 | script_list_.setEnabled(v); 456 | edit_menu_.setEnabled(v); 457 | } 458 | 459 | void RGSS_MainWindow::updateWindowTitle() { 460 | QString title; 461 | QRegExp reg(".*/([^/]*)/Data/Scripts.*"); 462 | 463 | if (reg.indexIn(open_path) != -1) 464 | title = reg.cap(1); 465 | else 466 | title = open_path; 467 | 468 | if (title.isEmpty() && archive_opened) 469 | title = "(Untitled)"; 470 | 471 | if (data_modified_) 472 | title.prepend("*"); 473 | 474 | setWindowTitle(title); 475 | } 476 | 477 | void RGSS_MainWindow::setupLoadedArchive() 478 | { 479 | archive_opened = true; 480 | 481 | enableEditing(true); 482 | updateWindowTitle(); 483 | script_list_.setCurrentIndex(archive_.index(0)); 484 | } 485 | 486 | void RGSS_MainWindow::onScriptCountChanged(int newCount) 487 | { 488 | delete_action_->setEnabled(newCount > 0); 489 | pin_action_->setEnabled(newCount > 0); 490 | } 491 | 492 | void RGSS_MainWindow::closeEvent(QCloseEvent *ce) 493 | { 494 | if (!verifySaveDiscard(tr("Exit Editor"))) 495 | ce->ignore(); 496 | else 497 | ce->accept(); 498 | 499 | /* Write settings */ 500 | QSettings settings(SETTINGS_ORG, SETTINGS_APP); 501 | settings.setValue("window_size", size()); 502 | settings.setValue("open_path", open_path); 503 | settings.setValue("last_valid_folder", last_valid_folder); 504 | settings.setValue("last_valid_folder_impexp", last_valid_folder_impexp); 505 | settings.setValue("show_script_indices", archive_.scriptNumsVisible()); 506 | } 507 | 508 | static const char *fileFilter = 509 | QT_TRANSLATE_NOOP("RGSS_MainWindow", 510 | "Script Archive (Scripts.rxdata Scripts.rvdata Scripts.rvdata2);;" 511 | "All files (*)"); 512 | 513 | void RGSS_MainWindow::onOpenArchive() { 514 | 515 | if (!verifySaveDiscard(tr("Open Archive"))) 516 | return; 517 | 518 | QString const f = QFileDialog::getOpenFileName( 519 | this, tr("Select script archive to open..."), last_valid_folder, tr(fileFilter)); 520 | 521 | if(f.isNull()) { return; } // check cancel 522 | 523 | if (archive_opened) 524 | closeScriptArchive(); 525 | 526 | loadScriptArchive(f); 527 | } 528 | 529 | bool RGSS_MainWindow::onSaveArchiveAs() { 530 | QString const f = QFileDialog::getSaveFileName( 531 | this, tr("Select saving file..."), last_valid_folder, tr(fileFilter)); 532 | 533 | if(f.isNull()) { return false; } // check cancel 534 | 535 | return saveScriptArchiveAs(f); 536 | } 537 | 538 | void RGSS_MainWindow::onImportScripts() 539 | { 540 | if (!verifySaveDiscard("Import Scripts")) 541 | return; 542 | 543 | const QString src_folder = QFileDialog::getExistingDirectory( 544 | this, tr("Select import folder..."), last_valid_folder_impexp); 545 | 546 | if (src_folder.isEmpty()) 547 | return; 548 | 549 | /* Open index */ 550 | QFile indFile(src_folder + "/index"); 551 | if (!indFile.open(QFile::ReadOnly)) { 552 | QMessageBox::critical(this, "Importing error.", "Cannot open index file"); 553 | return; 554 | } 555 | 556 | QTextStream indStream(&indFile); 557 | ScriptList scripts; 558 | 559 | while (!indStream.atEnd()) 560 | { 561 | QString line = indStream.readLine(); 562 | 563 | /* Minimum is 32bit ID (8 chars) + space */ 564 | if (line.size() < 8 + 1) { 565 | QMessageBox::critical(this, "Importing error.", "Index entry too short: " + line); 566 | return; 567 | } 568 | 569 | bool parseOK; 570 | QString scFilename = line.left(8); 571 | quint32 scID = scFilename.toUInt(&parseOK, 16); 572 | 573 | if (!parseOK) { 574 | QMessageBox::critical(this, "Importing error.", "Bad script ID: " + scFilename); 575 | return; 576 | } 577 | 578 | QString scName = line.mid(9); 579 | QFile scFile(src_folder + "/" + scFilename); 580 | 581 | if (!scFile.open(QFile::ReadOnly)) { 582 | QMessageBox::critical(this, "File reading error.", 583 | QString("Cannot open script \"%1\" (%2)").arg(scName, scFilename)); 584 | return; 585 | } 586 | 587 | QByteArray scData = scFile.readAll(); 588 | scFile.close(); 589 | 590 | Script script; 591 | script.magic = scID; 592 | script.name = scName; 593 | script.data = scData; 594 | 595 | scripts.append(script); 596 | } 597 | 598 | closeScriptArchive(); 599 | 600 | archive_.setScriptList(scripts); 601 | 602 | open_path = QString(); 603 | setupLoadedArchive(); 604 | setDataModified(true); 605 | 606 | last_valid_folder_impexp = QFileInfo(src_folder).absolutePath(); 607 | } 608 | 609 | void RGSS_MainWindow::onExportScripts() 610 | { 611 | const QString dest_folder = QFileDialog::getExistingDirectory( 612 | this, tr("Select export folder..."), last_valid_folder_impexp); 613 | 614 | if (dest_folder.isEmpty()) 615 | return; 616 | 617 | /* Write index */ 618 | QFile indFile(dest_folder + "/index"); 619 | if (!indFile.open(QFile::WriteOnly)) { 620 | QMessageBox::critical(this, "Importing error.", "Cannot open index file"); 621 | return; 622 | } 623 | 624 | storeChangedScripts(); 625 | 626 | QTextStream indStream(&indFile); 627 | ScriptList scripts = archive_.scriptList(); 628 | 629 | for (int i = 0; i < scripts.count(); ++i) 630 | { 631 | const Script &sc = scripts[i]; 632 | const QString scID = QString("%1").arg(sc.magic, 8, 16, QLatin1Char('0')).toUpper(); 633 | 634 | indStream << scID << QChar('~') << sc.name << "\n"; 635 | 636 | QFile scFile(dest_folder + "/" + scID); 637 | if (!scFile.open(QFile::WriteOnly)) { 638 | 639 | break; 640 | } 641 | 642 | scFile.write(sc.data.toUtf8()); 643 | 644 | scFile.close(); 645 | } 646 | 647 | indFile.close(); 648 | 649 | last_valid_folder_impexp = QFileInfo(dest_folder).absolutePath(); 650 | } 651 | 652 | void RGSS_MainWindow::onCloseArchive() 653 | { 654 | if (!verifySaveDiscard(tr("Close Archive"))) 655 | return; 656 | 657 | closeScriptArchive(); 658 | updateWindowTitle(); 659 | } 660 | 661 | void RGSS_MainWindow::closeScriptArchive() { 662 | 663 | /* Recycle editor widgets for later use */ 664 | QHash::const_iterator iter; 665 | for (iter = editor_hash.constBegin(); iter != editor_hash.constEnd(); ++iter) { 666 | EditorWidget *editor = iter.value(); 667 | 668 | disconnect(editor, SIGNAL(textChanged()), this, SLOT(onScriptEditorModified())); 669 | recycled_editors.append(iter.value()); 670 | } 671 | 672 | editor_hash.clear(); 673 | 674 | editor_stack.setCurrentWidget(&dummy_editor); 675 | 676 | open_path = QString(); 677 | archive_.clear(); 678 | 679 | enableEditing(false); 680 | 681 | archive_opened = false; 682 | 683 | setDataModified(false); 684 | 685 | pinned_list_.setVisible(false); 686 | } 687 | 688 | void RGSS_MainWindow::onScriptIndexChange(QModelIndex current, QModelIndex) 689 | { 690 | search_bar_.invalidateSearch(); 691 | 692 | Script *script = archive_.indexToScript(current); 693 | 694 | script_name_editor_.setEnabled(script != 0); 695 | 696 | if (!script) { 697 | script_name_editor_.clear(); 698 | editor_stack.setCurrentWidget(&dummy_editor); 699 | return; 700 | } 701 | 702 | EditorWidget *editor = getEditorForScript(script); 703 | 704 | editor_stack.setCurrentWidget(editor); 705 | editor->setFocus(); 706 | 707 | script_name_editor_.setText(script->name); 708 | pinned_list_.setCurrentIndex(pinned_model_.mapFromSource(current)); 709 | } 710 | 711 | void RGSS_MainWindow::onPinnedIndexChange(QModelIndex current, QModelIndex) 712 | { 713 | if (!current.isValid()) 714 | return; 715 | 716 | script_list_.setCurrentIndex(pinned_model_.mapToSource(current)); 717 | } 718 | 719 | void RGSS_MainWindow::onScriptNameEdited(QString const& name) { 720 | archive_.setData(getCurrentIndex(), name, Qt::DisplayRole); 721 | setDataModified(true); 722 | } 723 | 724 | void RGSS_MainWindow::loadScriptArchive(QString const& file, bool show_errors) { 725 | QFile archiveFile(file); 726 | if (!archiveFile.open(QFile::ReadOnly)) { 727 | if (show_errors) 728 | QMessageBox::critical(this, "File reading error.", "Cannot open file: " + file); 729 | return; 730 | } 731 | 732 | try { 733 | archive_.read(archiveFile); 734 | archiveFile.close(); 735 | } 736 | catch (const QByteArray &error) { 737 | if (show_errors) 738 | QMessageBox::critical(this, "File reading error.", "Cannot read: " + file + "\n" + error); 739 | return; 740 | } 741 | 742 | QFileInfo finfo(file); 743 | open_path = finfo.absoluteFilePath(); 744 | last_valid_folder = finfo.absolutePath(); 745 | 746 | setupLoadedArchive(); 747 | setDataModified(false); 748 | } 749 | 750 | void RGSS_MainWindow::onSaveArchive() { 751 | if (open_path.isEmpty()) { 752 | onSaveArchiveAs(); 753 | return; 754 | } 755 | 756 | saveScriptArchiveAs(open_path); 757 | } 758 | 759 | bool RGSS_MainWindow::saveScriptArchiveAs(QString const& file) { 760 | /* Determine marshal format */ 761 | QFileInfo finfo(file); 762 | QString fileExt = finfo.suffix(); 763 | Script::Format format; 764 | 765 | if (fileExt == "rxdata") 766 | format = Script::XP; 767 | else if (fileExt == "rvdata") 768 | format = Script::XP; 769 | else if (fileExt == "rvdata2") 770 | format = Script::VXAce; 771 | else { 772 | QMessageBox::critical(this, "File saving error.", "Unrecognized file extension: " + fileExt); 773 | return false; 774 | } 775 | 776 | QFile archiveFile(file); 777 | if (!archiveFile.open(QFile::WriteOnly)) { 778 | QMessageBox::critical(this, "File saving error.", "Cannot open for writing: " + file); 779 | return false; 780 | } 781 | 782 | /* Store any modifications into the archive */ 783 | storeChangedScripts(); 784 | 785 | try { 786 | archive_.write(archiveFile, format); 787 | archiveFile.close(); 788 | } 789 | catch (const QByteArray &) { 790 | QMessageBox::critical(this, "File saving error.", "Cannot save: " + file); 791 | return false; 792 | } 793 | 794 | /* Update filename */ 795 | open_path = finfo.absoluteFilePath(); 796 | last_valid_folder = finfo.absolutePath(); 797 | 798 | setDataModified(false); 799 | 800 | return true; 801 | } 802 | -------------------------------------------------------------------------------- /src/qt/main_window.hxx: -------------------------------------------------------------------------------- 1 | #ifndef RGSS_MAIN_WINDOW_HXX 2 | #define RGSS_MAIN_WINDOW_HXX 3 | 4 | #include 5 | #include 6 | #include 7 | #include 8 | #include 9 | #include 10 | #include 11 | #include 12 | #include 13 | #include 14 | 15 | #include 16 | 17 | #include "script_archive.hxx" 18 | #include "editor_widget.hxx" 19 | #include "pinned_script_list.hxx" 20 | #include "search_bar.hxx" 21 | 22 | class ListView : public QListView 23 | { 24 | Q_OBJECT 25 | 26 | public: 27 | ListView(QWidget *parent = 0) 28 | : QListView(parent), 29 | ctx_menu(0) 30 | { 31 | connect(this, SIGNAL(customContextMenuRequested(QPoint)), SLOT(onShowContextMenu(QPoint))); 32 | setContextMenuPolicy(Qt::CustomContextMenu); 33 | } 34 | 35 | void setContextMenu(QMenu *menu) 36 | { 37 | ctx_menu = menu; 38 | } 39 | 40 | private: 41 | QMenu *ctx_menu; 42 | 43 | private slots: 44 | void onShowContextMenu(const QPoint &point) 45 | { 46 | if (ctx_menu) 47 | ctx_menu->exec(mapToGlobal(point)); 48 | } 49 | }; 50 | 51 | class RGSS_MainWindow : public QMainWindow { 52 | Q_OBJECT 53 | 54 | public: 55 | RGSS_MainWindow(const QString &path_to_load, 56 | QWidget* parent = NULL, Qt::WindowFlags = 0); 57 | 58 | public slots: 59 | /* Menu functions */ 60 | void onOpenArchive(); 61 | void onSaveArchive(); 62 | bool onSaveArchiveAs(); 63 | void onImportScripts(); 64 | void onExportScripts(); 65 | void onCloseArchive(); 66 | 67 | private slots: 68 | void onScriptIndexChange(QModelIndex current, QModelIndex); 69 | void onPinnedIndexChange(QModelIndex current, QModelIndex); 70 | void onScriptNameEdited(QString const& name); 71 | void onScriptEditorModified(); 72 | void onArchiveDropped(const QString &); 73 | 74 | void onShowSearchBar(); 75 | void onSearchBarHidePressed(); 76 | void onSearchComitted(const QString &text); 77 | void onSearchNext(); 78 | 79 | void onGotoLine(); 80 | 81 | void onInsertScript(); 82 | void onDeleteScript(); 83 | void onPinScript(); 84 | void onUnpinScript(); 85 | 86 | void onScriptCountChanged(int); 87 | 88 | private: 89 | void loadScriptArchive(QString const& file, bool show_errors = true); 90 | bool saveScriptArchiveAs(QString const& file); 91 | void closeScriptArchive(); 92 | void setDataModified(bool); 93 | void enableEditing(bool v); 94 | void updateWindowTitle(); 95 | void setupLoadedArchive(); 96 | 97 | void closeEvent(QCloseEvent *); 98 | 99 | EditorWidget *getEditorForScript(Script *script); 100 | EditorWidget *getCurrentEditor(); 101 | 102 | QModelIndex getCurrentIndex(); 103 | 104 | /* Stores text changed in editors back 105 | * into the respective script struct */ 106 | void storeChangedScripts(); 107 | 108 | /* Asks the user if changes should be 109 | * saved or discarded (or the action canceled). 110 | * true: go ahead with action, false: cancel action */ 111 | bool verifySaveDiscard(const QString &actionTitle); 112 | 113 | /* Setup common settings */ 114 | static void setupEditor(QsciScintilla &editor); 115 | 116 | /* Path of currently opened archive */ 117 | QString open_path; 118 | 119 | /* An archive can be open even without 120 | * a valid path (eg. after import */ 121 | bool archive_opened; 122 | 123 | /* Last folder from which a valid archive 124 | * was opened / saved */ 125 | QString last_valid_folder; 126 | QString last_valid_folder_impexp; 127 | 128 | ScriptArchive archive_; 129 | bool data_modified_; 130 | PinnedScriptList pinned_model_; 131 | 132 | QSplitter splitter_; 133 | QWidget left_side_; 134 | ListView pinned_list_; 135 | ListView script_list_; 136 | QLineEdit script_name_editor_; 137 | 138 | SearchBar search_bar_; 139 | 140 | QMenu edit_menu_; 141 | QAction *delete_action_; 142 | QAction *pin_action_; 143 | 144 | QStackedWidget editor_stack; 145 | 146 | /* Is only shown when no archive is opened */ 147 | EditorWidget dummy_editor; 148 | 149 | QHash editor_hash; 150 | 151 | QList recycled_editors; 152 | }; 153 | 154 | #endif 155 | -------------------------------------------------------------------------------- /src/qt/pinned_script_list.cxx: -------------------------------------------------------------------------------- 1 | #include "pinned_script_list.hxx" 2 | 3 | PinnedScriptList::PinnedScriptList(QObject *parent) 4 | : QSortFilterProxyModel(parent) 5 | { 6 | } 7 | 8 | bool PinnedScriptList::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const 9 | { 10 | ScriptArchive *sa = static_cast(sourceModel()); 11 | 12 | return scripts.contains(sa->indexToScript(sa->index(source_row))); 13 | } 14 | 15 | void PinnedScriptList::addScript(const Script &script) 16 | { 17 | scripts.insert(&script); 18 | } 19 | 20 | void PinnedScriptList::removeScript(const Script &script) 21 | { 22 | scripts.remove(&script); 23 | } 24 | 25 | void PinnedScriptList::clear() 26 | { 27 | scripts.clear(); 28 | } 29 | 30 | bool PinnedScriptList::isEmpty() 31 | { 32 | return scripts.isEmpty(); 33 | } 34 | -------------------------------------------------------------------------------- /src/qt/pinned_script_list.hxx: -------------------------------------------------------------------------------- 1 | #ifndef PINNED_SCRIPT_LIST_HXX 2 | #define PINNED_SCRIPT_LIST_HXX 3 | 4 | #include 5 | #include 6 | 7 | #include "script_archive.hxx" 8 | 9 | class PinnedScriptList : public QSortFilterProxyModel 10 | { 11 | Q_OBJECT 12 | 13 | public: 14 | PinnedScriptList(QObject *parent = 0); 15 | 16 | void addScript(const Script &script); 17 | void removeScript(const Script &script); 18 | void clear(); 19 | bool isEmpty(); 20 | 21 | private: 22 | QSet scripts; 23 | 24 | bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const; 25 | }; 26 | 27 | #endif // PINNED_SCRIPT_LIST_HXX 28 | -------------------------------------------------------------------------------- /src/qt/savediscard_dialog.cxx: -------------------------------------------------------------------------------- 1 | #include "savediscard_dialog.hxx" 2 | 3 | #include 4 | #include 5 | #include 6 | #include 7 | 8 | SaveDiscardDialog::SaveDiscardDialog(QWidget *parent) 9 | : QDialog(parent) 10 | { 11 | QVBoxLayout *vbox = new QVBoxLayout(); 12 | 13 | QLabel *text = new QLabel(tr("There are unsaved changes")); 14 | 15 | vbox->addWidget(text); 16 | vbox->addStretch(); 17 | 18 | QHBoxLayout *hbox = new QHBoxLayout(); 19 | 20 | QPushButton *button; 21 | 22 | button = new QPushButton(tr("Cancel")); 23 | connect(button, SIGNAL(clicked()), SLOT(onCancel())); 24 | hbox->addWidget(button); 25 | 26 | button = new QPushButton(tr("Discard")); 27 | connect(button, SIGNAL(clicked()), SLOT(onDiscard())); 28 | hbox->addWidget(button); 29 | 30 | button = new QPushButton(tr("Save")); 31 | connect(button, SIGNAL(clicked()), SLOT(onSave())); 32 | hbox->addWidget(button); 33 | 34 | vbox->addLayout(hbox); 35 | 36 | setLayout(vbox); 37 | } 38 | 39 | void SaveDiscardDialog::onCancel() 40 | { 41 | QDialog::done(Cancel); 42 | } 43 | 44 | void SaveDiscardDialog::onDiscard() 45 | { 46 | QDialog::done(Discard); 47 | } 48 | 49 | void SaveDiscardDialog::onSave() 50 | { 51 | QDialog::done(Save); 52 | } 53 | 54 | void SaveDiscardDialog::reject() 55 | { 56 | onCancel(); 57 | } 58 | -------------------------------------------------------------------------------- /src/qt/savediscard_dialog.hxx: -------------------------------------------------------------------------------- 1 | #ifndef SAVEDISCARD_DIALOG_HXX 2 | #define SAVEDISCARD_DIALOG_HXX 3 | 4 | #include 5 | 6 | class SaveDiscardDialog : public QDialog 7 | { 8 | Q_OBJECT 9 | public: 10 | SaveDiscardDialog(QWidget *parent = 0); 11 | 12 | enum { 13 | Cancel, 14 | Discard, 15 | Save 16 | }; 17 | 18 | private slots: 19 | void onCancel(); 20 | void onDiscard(); 21 | void onSave(); 22 | private: 23 | void reject(); 24 | }; 25 | 26 | #endif // SAVEDISCARD_DIALOG_HXX 27 | -------------------------------------------------------------------------------- /src/qt/script_archive.cxx: -------------------------------------------------------------------------------- 1 | #include "script_archive.hxx" 2 | 3 | QModelIndex ScriptArchive::index(int row, int column, const QModelIndex &parent) const 4 | { 5 | if (column != 0 || parent.isValid() || row < 0 || row > scripts.count() - 1) 6 | return QModelIndex(); 7 | 8 | return createIndex(row, column, (void*) &scripts[row]); 9 | } 10 | 11 | int ScriptArchive::rowCount(const QModelIndex &parent) const 12 | { 13 | if (parent.isValid()) 14 | return 0; 15 | 16 | return scripts.count(); 17 | } 18 | 19 | QVariant ScriptArchive::data(const QModelIndex &index, int role) const 20 | { 21 | if (!index.isValid()) 22 | return QVariant(); 23 | 24 | const Script *sc = static_cast(index.internalPointer()); 25 | 26 | switch (role) 27 | { 28 | case Qt::DisplayRole : 29 | if (scriptNums) 30 | return QString("%1: %2").arg(index.row(), scriptNumDigits, 10, QChar('0')).arg(sc->name); 31 | else 32 | return sc->name; 33 | default: 34 | return QVariant(); 35 | } 36 | } 37 | 38 | bool ScriptArchive::setData(const QModelIndex &index, const QVariant &value, int role) 39 | { 40 | if (!index.isValid() || role != Qt::DisplayRole) 41 | return false; 42 | 43 | indexToScript(index)->name = value.toString(); 44 | dataChanged(index, index); 45 | 46 | return true; 47 | } 48 | 49 | bool ScriptArchive::insertRows(int row, int count, const QModelIndex &parent) 50 | { 51 | /* Multi insert not implemented */ 52 | Q_ASSERT(count == 1); 53 | 54 | if (parent.isValid()) 55 | return false; 56 | 57 | Script sc; 58 | sc.magic = generateMagic(scripts); 59 | 60 | beginInsertRows(QModelIndex(), row, row); 61 | scripts.insert(row, sc); 62 | endInsertRows(); 63 | 64 | emitScriptCountChanged(); 65 | 66 | return true; 67 | } 68 | 69 | bool ScriptArchive::removeRows(int row, int count, const QModelIndex &parent) 70 | { 71 | /* Multi remove not implemented */ 72 | Q_ASSERT(count == 1); 73 | 74 | if (parent.isValid()) 75 | return false; 76 | 77 | beginRemoveRows(QModelIndex(), row, row); 78 | scripts.removeAt(row); 79 | endRemoveRows(); 80 | 81 | emitScriptCountChanged(); 82 | 83 | return true; 84 | } 85 | 86 | Script *ScriptArchive::indexToScript(const QModelIndex &index) const 87 | { 88 | if (!index.isValid()) 89 | return 0; 90 | 91 | return static_cast(index.internalPointer()); 92 | } 93 | 94 | void ScriptArchive::clear() 95 | { 96 | if (scripts.empty()) 97 | return; 98 | 99 | beginRemoveRows(QModelIndex(), 0, scripts.count()-1); 100 | scripts.clear(); 101 | endRemoveRows(); 102 | 103 | emitScriptCountChanged(); 104 | } 105 | 106 | void ScriptArchive::setScriptNumsVisible(bool mode) 107 | { 108 | scriptNums = mode; 109 | dataChanged(index(0), index(scriptCount()-1)); 110 | } 111 | 112 | void ScriptArchive::setScriptList(ScriptList &list) 113 | { 114 | clear(); 115 | 116 | if (list.empty()) 117 | return; 118 | 119 | beginInsertRows(QModelIndex(), 0, list.count()); 120 | scripts = list; 121 | endInsertRows(); 122 | 123 | emitScriptCountChanged(); 124 | } 125 | 126 | void ScriptArchive::read(QIODevice &dev) 127 | { 128 | ScriptList newList = readScripts(dev); 129 | 130 | setScriptList(newList); 131 | } 132 | 133 | void ScriptArchive::write(QIODevice &dev, Script::Format format) 134 | { 135 | writeScripts(scripts, dev, format); 136 | } 137 | 138 | void ScriptArchive::emitScriptCountChanged() 139 | { 140 | scriptNumDigits = qMax(QString::number(scripts.count()-1).length(), 0); 141 | scriptCountChanged(scripts.count()); 142 | } 143 | -------------------------------------------------------------------------------- /src/qt/script_archive.hxx: -------------------------------------------------------------------------------- 1 | #ifndef SCRIPT_ARCHIVE_HXX 2 | #define SCRIPT_ARCHIVE_HXX 3 | 4 | #include 5 | #include "ruby_data.hxx" 6 | 7 | class ScriptArchive : public QAbstractListModel 8 | { 9 | Q_OBJECT 10 | 11 | public: 12 | ScriptArchive() 13 | : scriptNums(false), 14 | scriptNumDigits(0) 15 | {} 16 | 17 | /* These both throw QByteStrings containing 18 | * possible error messages */ 19 | void read(QIODevice &dev); 20 | void write(QIODevice &dev, Script::Format format); 21 | 22 | void clear(); 23 | 24 | ScriptList scriptList() { return scripts; } 25 | void setScriptList(ScriptList &list); 26 | 27 | /* QAbstractListModel */ 28 | QModelIndex index(int row, int column, const QModelIndex &parent) const; 29 | QModelIndex index(int row) { return index(row, 0, QModelIndex()); } 30 | int rowCount(const QModelIndex &parent) const; 31 | QVariant data(const QModelIndex &index, int role) const; 32 | bool setData(const QModelIndex &index, const QVariant &value, int role); 33 | bool insertRows(int row, int count, const QModelIndex &parent); 34 | bool removeRows(int row, int count, const QModelIndex &parent); 35 | 36 | Script *indexToScript(const QModelIndex &index) const; 37 | int scriptCount() const { return scripts.count(); } 38 | 39 | bool scriptNumsVisible() { return scriptNums; } 40 | 41 | public slots: 42 | void setScriptNumsVisible(bool mode); 43 | 44 | signals: 45 | void scriptCountChanged(int newCount); 46 | 47 | private: 48 | ScriptList scripts; 49 | bool scriptNums; 50 | int scriptNumDigits; 51 | 52 | void emitScriptCountChanged(); 53 | }; 54 | 55 | #endif 56 | -------------------------------------------------------------------------------- /src/qt/search_bar.cxx: -------------------------------------------------------------------------------- 1 | #include "search_bar.hxx" 2 | 3 | #include 4 | #include 5 | #include 6 | 7 | 8 | SearchBar::SearchBar(QWidget *parent) 9 | : QWidget(parent), 10 | text_changed(false) 11 | { 12 | QBoxLayout *layout = new QHBoxLayout(this); 13 | 14 | edit = new LineEdit(); 15 | connect(edit, SIGNAL(returnPressed()), SLOT(onEnterPressed())); 16 | connect(edit, SIGNAL(textChanged(QString)), SLOT(invalidateSearch())); 17 | 18 | QPushButton *hide = new QPushButton(tr("Hide")); 19 | connect(hide, SIGNAL(clicked()), SIGNAL(hidePressed())); 20 | connect(edit, SIGNAL(escapePressed()), SIGNAL(hidePressed())); 21 | 22 | not_found = new QLabel(tr("Not found")); 23 | not_found->setStyleSheet("color: red"); 24 | not_found->hide(); 25 | 26 | layout->addWidget(new QLabel(tr("Find:"))); 27 | layout->addWidget(edit); 28 | layout->addWidget(not_found); 29 | layout->addStretch(); 30 | layout->addWidget(hide); 31 | layout->setContentsMargins(QMargins()); 32 | 33 | setLayout(layout); 34 | } 35 | 36 | void SearchBar::setEditText(const QString &text) 37 | { 38 | edit->setText(text); 39 | } 40 | 41 | void SearchBar::setNotFoundFlag() 42 | { 43 | not_found->show(); 44 | } 45 | 46 | void SearchBar::onShow() 47 | { 48 | show(); 49 | edit->selectAll(); 50 | edit->setFocus(); 51 | } 52 | 53 | void SearchBar::onEnterPressed() 54 | { 55 | if (edit->text().isEmpty()) 56 | return; 57 | 58 | if (text_changed) 59 | { 60 | text_changed = false; 61 | searchComitted(edit->text()); 62 | } 63 | else 64 | { 65 | searchNext(); 66 | } 67 | } 68 | 69 | void SearchBar::invalidateSearch() 70 | { 71 | text_changed = true; 72 | not_found->hide(); 73 | } 74 | -------------------------------------------------------------------------------- /src/qt/search_bar.hxx: -------------------------------------------------------------------------------- 1 | #ifndef SEARCH_BAR_HXX 2 | #define SEARCH_BAR_HXX 3 | 4 | #include 5 | 6 | #include "line_edit.hxx" 7 | 8 | class QLabel; 9 | 10 | class SearchBar : public QWidget 11 | { 12 | Q_OBJECT 13 | 14 | public: 15 | SearchBar(QWidget *parent = 0); 16 | 17 | void setEditText(const QString &text); 18 | void setNotFoundFlag(); 19 | 20 | public slots: 21 | void onShow(); 22 | void invalidateSearch(); 23 | 24 | signals: 25 | void hidePressed(); 26 | void searchComitted(const QString &text); 27 | void searchNext(); 28 | 29 | private slots: 30 | void onEnterPressed(); 31 | 32 | private: 33 | LineEdit *edit; 34 | bool text_changed; 35 | 36 | QLabel *not_found; 37 | }; 38 | 39 | #endif // SEARCH_BAR_HXX 40 | -------------------------------------------------------------------------------- /src/ruby_data.cxx: -------------------------------------------------------------------------------- 1 | extern "C" { 2 | #include 3 | } 4 | 5 | #include "ruby_data.hxx" 6 | #include 7 | 8 | #include 9 | #include 10 | 11 | #define LINEEND_INT "\n" 12 | #define LINEEND_EXT "\r\n" 13 | 14 | 15 | 16 | bool parseScript(std::string const&) { 17 | // TODO 18 | return true; 19 | } 20 | 21 | static QByteArray compressData(const QByteArray &data) 22 | { 23 | uLongf destLen = data.size() + 8; 24 | QByteArray buffer; 25 | 26 | int status; 27 | 28 | do { 29 | destLen *= 2; 30 | buffer.resize(destLen); 31 | 32 | status = compress(reinterpret_cast(buffer.data()), &destLen, 33 | reinterpret_cast(data.constData()), data.length()); 34 | 35 | if (status != Z_OK && status != Z_BUF_ERROR) 36 | throw QByteArray("zlib decompression error"); 37 | } 38 | while (status == Z_BUF_ERROR); 39 | 40 | buffer.resize(destLen); 41 | 42 | return buffer; 43 | } 44 | 45 | static QByteArray decompressData(const QByteArray &data) 46 | { 47 | uLongf destLen = data.size() + 8; 48 | QByteArray buffer; 49 | 50 | int status; 51 | 52 | do { 53 | destLen *= 2; 54 | buffer.resize(destLen); 55 | 56 | status = uncompress(reinterpret_cast(buffer.data()), &destLen, 57 | reinterpret_cast(data.constData()), data.size()); 58 | 59 | if (status != Z_OK && status != Z_BUF_ERROR) 60 | throw QByteArray("zlib decompression error"); 61 | } 62 | while (status == Z_BUF_ERROR); 63 | 64 | buffer.resize(destLen); 65 | 66 | return buffer; 67 | } 68 | 69 | /* Reads one byte from the device */ 70 | static char readByte(QIODevice &dev) 71 | { 72 | char byte; 73 | int count = dev.read(&byte, 1); 74 | 75 | if (count < 1) 76 | throw QByteArray("Unable to read data"); 77 | 78 | return byte; 79 | } 80 | 81 | static void verifyByte(QIODevice &dev, char expected, 82 | const char *error = "Bad data") 83 | { 84 | char byte = readByte(dev); 85 | 86 | if (byte != expected) 87 | throw QByteArray(error); 88 | } 89 | 90 | static int readFixnum(QIODevice &dev) 91 | { 92 | char head = readByte(dev); 93 | 94 | if (head == 0) 95 | return 0; 96 | else if (head > 5) 97 | return head - 5; 98 | else if (head < -4) 99 | return head + 5; 100 | 101 | int pos = (head > 0); 102 | int len = pos ? head : head * -1; 103 | 104 | char n1, n2, n3, n4; 105 | 106 | if (pos) 107 | n2 = n3 = n4 = 0; 108 | else 109 | n2 = n3 = n4 = 0xFF; 110 | 111 | n1 = readByte(dev); 112 | 113 | if (len >= 2) 114 | n2 = readByte(dev); 115 | if (len >= 3) 116 | n3 = readByte(dev); 117 | if (len >= 4) 118 | n4 = readByte(dev); 119 | 120 | int result = ((0xFF << 0x00) & (n1 << 0x00)) 121 | | ((0xFF << 0x08) & (n2 << 0x08)) 122 | | ((0xFF << 0x10) & (n3 << 0x10)) 123 | | ((0xFF << 0x18) & (n4 << 0x18)); 124 | 125 | return result; 126 | } 127 | 128 | static QByteArray readString(QIODevice &dev) 129 | { 130 | int len = readFixnum(dev); 131 | QByteArray data = dev.read(len); 132 | 133 | if (data.size() != len) 134 | throw QByteArray("Error reading data"); 135 | 136 | return data; 137 | } 138 | 139 | static QByteArray readIVARString(QIODevice &dev) 140 | { 141 | QByteArray data; 142 | 143 | /* Read inner raw string */ 144 | verifyByte(dev, '"'); 145 | data = readString(dev); 146 | 147 | /* Read encoding */ 148 | int ivarCount = readFixnum(dev); 149 | 150 | // XXX Can this be zero? 151 | if (ivarCount > 1) 152 | throw QByteArray("Cannot handle multiple string IVARS"); 153 | 154 | /* Read encoding symbol */ 155 | // XXX We can't deal with anythind outside Utf8/ASCII 156 | char symByte = readByte(dev); 157 | 158 | if (symByte == ':') { 159 | /* This symbol must be :E */ 160 | QByteArray encSym = readString(dev); 161 | 162 | if (encSym.size() != 1 || encSym[0] != 'E') 163 | throw QByteArray("Bad data"); 164 | } 165 | else if (symByte == ';') { 166 | /* The :E symbol is always the first symlink */ 167 | verifyByte(dev, 0); 168 | } 169 | else { 170 | throw QByteArray("Bad data"); 171 | } 172 | 173 | char encByte = readByte(dev); 174 | 175 | if (encByte != 'T' && encByte != 'F') 176 | throw QByteArray("Bad data"); 177 | 178 | return data; 179 | } 180 | 181 | /* Abstractly reads a string, either raw or IVAR+Encoding based */ 182 | static QByteArray readRubyString(QIODevice &dev) 183 | { 184 | QByteArray data; 185 | char type = readByte(dev); 186 | 187 | if (type == '"') { 188 | data = readString(dev); 189 | } 190 | else if (type == 'I') { 191 | data = readIVARString(dev); 192 | } 193 | else { 194 | throw QByteArray("Bad data"); 195 | } 196 | 197 | return data; 198 | } 199 | 200 | static QString UTF8ToQString(const QByteArray &data) 201 | { 202 | return QString::fromUtf8(data.constData(), data.size()); 203 | } 204 | 205 | static Script readScript(QIODevice &dev) 206 | { 207 | Script script; 208 | 209 | /* Verify array prologue */ 210 | verifyByte(dev, '['); 211 | char len = readFixnum(dev); 212 | if (len != 3) 213 | throw QByteArray("Bad data"); 214 | 215 | /* Read magic */ 216 | verifyByte(dev, 'i'); 217 | script.magic = readFixnum(dev); 218 | 219 | /* Read name */ 220 | script.name = UTF8ToQString(readRubyString(dev)); 221 | 222 | /* Read script data */ 223 | QByteArray data = readRubyString(dev); 224 | data = decompressData(data); 225 | script.data = UTF8ToQString(data); 226 | 227 | /* Convert line endings: Windows -> Unix */ 228 | script.data.remove(QChar('\r'), Qt::CaseSensitive); 229 | 230 | return script; 231 | } 232 | 233 | /* Reads and verifies the Marshal header */ 234 | static void verifyHeader(QIODevice &dev) 235 | { 236 | const char *error = "Bad marshal header"; 237 | 238 | verifyByte(dev, 4, error); 239 | verifyByte(dev, 8, error); 240 | } 241 | 242 | static void writeByte(QIODevice &dev, char byte) 243 | { 244 | int count = dev.write(&byte, 1); 245 | 246 | if (count < 1) 247 | throw QByteArray("Error writing data"); 248 | } 249 | 250 | static void writeFixnum(QIODevice &dev, int value) 251 | { 252 | if (value == 0) { 253 | writeByte(dev, 0); 254 | return; 255 | } 256 | else if (value > 0 && value < 123) { 257 | writeByte(dev, (char) value + 5); 258 | return; 259 | } 260 | else if (value < 0 && value > -124) { 261 | writeByte(dev, (char) value - 5); 262 | return; 263 | } 264 | 265 | char len; 266 | 267 | if (value > 0) { 268 | /* Positive number */ 269 | if (value <= 0x7F) 270 | len = 1; 271 | else if (value <= 0x7FFF) 272 | len = 2; 273 | else if (value <= 0x7FFFFF) 274 | len = 3; 275 | else 276 | len = 4; 277 | } 278 | else { 279 | /* Negative number */ 280 | if (value >= (int) 0x80) 281 | len = -1; 282 | else if (value >= (int) 0x8000) 283 | len = -2; 284 | else if (value <= (int) 0x800000) 285 | len = -3; 286 | else 287 | len = -4; 288 | } 289 | 290 | /* Write length */ 291 | writeByte(dev, len); 292 | 293 | /* Write bytes */ 294 | if (len >= 1 || len <= -1) 295 | writeByte(dev, (value & 0x000000FF) >> 0x00); 296 | if (len >= 2 || len <= -2) 297 | writeByte(dev, (value & 0x0000FF00) >> 0x08); 298 | if (len >= 3 || len <= -3) 299 | writeByte(dev, (value & 0x00FF0000) >> 0x10); 300 | if (len == 4 || len == -4) 301 | writeByte(dev, (value & 0xFF000000) >> 0x18); 302 | } 303 | 304 | static void writeString(QIODevice &dev, const QByteArray &data) 305 | { 306 | writeFixnum(dev, data.size()); 307 | if (dev.write(data) < data.size()) 308 | throw QByteArray("Error writing data"); 309 | } 310 | 311 | static void writeIVARString(QIODevice &dev, const QByteArray &data) 312 | { 313 | /* Write inner string */ 314 | writeByte(dev, '"'); 315 | writeString(dev, data); 316 | 317 | /* Write IVAR count */ 318 | writeFixnum(dev, 1); 319 | // XXX It's no big deal, but maybe we should symlink all 320 | // further references to ':E' as Ruby would do? 321 | writeByte(dev, ':'); 322 | writeString(dev, "E"); 323 | /* Always write Utf8 encoding */ 324 | writeByte(dev, 'T'); 325 | } 326 | 327 | static void writeRubyString(QIODevice &dev, const QByteArray &data, 328 | Script::Format format) 329 | { 330 | if (format == Script::XP) { 331 | writeByte(dev, '"'); 332 | writeString(dev, data); 333 | } 334 | else { /* format == ScriptArchive::VXAce */ 335 | writeByte(dev, 'I'); 336 | writeIVARString(dev, data); 337 | } 338 | } 339 | 340 | static void writeScript(QIODevice &dev, const Script &script, 341 | Script::Format format) 342 | { 343 | /* Write array prologue */ 344 | writeByte(dev, '['); 345 | writeFixnum(dev, 3); 346 | 347 | /* Write magic */ 348 | writeByte(dev, 'i'); 349 | writeFixnum(dev, script.magic); 350 | 351 | /* Write name */ 352 | writeRubyString(dev, script.name.toUtf8(), format); 353 | 354 | /* Convert line endings: Unix -> Windows (for compat) */ 355 | QString sdata; 356 | sdata.reserve(script.data.size()); 357 | 358 | for (size_t i = 0; i < script.data.size(); ++i) { 359 | QChar c = script.data.at(i); 360 | 361 | if (c == QChar('\n')) { 362 | sdata.append(QChar('\r')); 363 | } 364 | 365 | sdata.append(c); 366 | } 367 | 368 | /* Write script data */ 369 | QByteArray data = sdata.toUtf8(); 370 | data = compressData(data); 371 | writeRubyString(dev, data, format); 372 | } 373 | 374 | ScriptList readScripts(QIODevice &dev) 375 | { 376 | verifyHeader(dev); 377 | 378 | /* Read array prologue */ 379 | verifyByte(dev, '['); 380 | int scriptCount = readFixnum(dev); 381 | 382 | /* Read scripts */ 383 | ScriptList scripts; 384 | 385 | try { 386 | for (int i = 0; i < scriptCount; ++i) { 387 | scripts.append(readScript(dev)); 388 | } 389 | } 390 | catch (const QByteArray &error) { 391 | throw error; 392 | } 393 | 394 | return scripts; 395 | } 396 | 397 | void writeScripts(const ScriptList &scripts, 398 | QIODevice &dev, Script::Format format) 399 | { 400 | /* Write header */ 401 | writeByte(dev, 4); 402 | writeByte(dev, 8); 403 | 404 | /* Write array prologue */ 405 | writeByte(dev, '['); 406 | writeFixnum(dev, scripts.count()); 407 | 408 | /* Write scripts */ 409 | for (int i = 0; i < scripts.count(); ++i) 410 | writeScript(dev, scripts[i], format); 411 | } 412 | 413 | int generateMagic(const ScriptList &scripts) 414 | { 415 | QSet ids; 416 | for (size_t i = 0; i < scripts.size(); ++i) 417 | ids.insert(scripts[i].magic); 418 | 419 | int result; 420 | 421 | /* Generate a value that's not yet taken */ 422 | do { 423 | result = qrand(); 424 | } while (ids.contains(result) || result < 0); 425 | 426 | return result; 427 | } 428 | -------------------------------------------------------------------------------- /src/ruby_data.hxx: -------------------------------------------------------------------------------- 1 | #ifndef RGSS_RUBY_DATA_HXX 2 | #define RGSS_RUBY_DATA_HXX 3 | 4 | #include 5 | #include 6 | 7 | bool parseScript(std::string const& src); 8 | 9 | struct Script { 10 | enum Format { 11 | XP, /* Ruby 1.8 */ 12 | VXAce /* Ruby 1.9 */ 13 | }; 14 | 15 | int magic; 16 | QString name; 17 | QString data; 18 | }; 19 | 20 | typedef QList