--- gigedit/trunk/src/gigedit/MacrosSetup.cpp 2017/05/08 17:30:10 3157 +++ gigedit/trunk/src/gigedit/MacrosSetup.cpp 2017/05/09 14:35:23 3162 @@ -9,20 +9,53 @@ #include "global.h" #include #include +#include +#include +#include "MacroEditor.h" MacrosSetup::MacrosSetup() : + m_modified(false), + m_clipboardContent(NULL), + m_addFromClipboardButton(" " + Glib::ustring(_("From Clipboard")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + "B"), + m_addFromSelectionButton(" " + Glib::ustring(_("From Selection")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + "S"), + m_buttonUp(Gtk::Stock::GO_UP), + m_buttonDown(Gtk::Stock::GO_DOWN), + m_buttonEdit(Gtk::Stock::EDIT), m_statusLabel("", Gtk::ALIGN_START), - m_deleteButton(Glib::ustring(_("Delete")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL), - m_inverseDeleteButton(Glib::ustring(_("Inverse Delete")) + " " + UNICODE_ALT_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL), - m_applyButton(_("_Apply"), true), - m_cancelButton(_("_Cancel"), true), - m_altKeyDown(false) + m_labelComment(_("Comment"), Gtk::ALIGN_START), + m_deleteButton(" " + Glib::ustring(_("Delete")) + " " + UNICODE_PRIMARY_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL), + m_inverseDeleteButton(" " + Glib::ustring(_("Inverse Delete")) + " " + UNICODE_ALT_KEY_SYMBOL + UNICODE_ERASE_KEY_SYMBOL), + m_applyButton(Gtk::Stock::APPLY), + m_cancelButton(Gtk::Stock::CANCEL), + m_altKeyDown(false), + m_primaryKeyDown(false) { add(m_vbox); set_title(_("Setup Macros")); - set_default_size(800, 600); + set_default_size(680, 500); + + m_addFromClipboardButton.set_image( + *new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON) + ); + m_addFromSelectionButton.set_image( + *new Gtk::Image(Gtk::Stock::ADD, Gtk::ICON_SIZE_BUTTON) + ); + m_deleteButton.set_image( + *new Gtk::Image(Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON) + ); + m_inverseDeleteButton.set_image( + *new Gtk::Image(Gtk::Stock::DELETE, Gtk::ICON_SIZE_BUTTON) + ); + m_addFromClipboardButton.set_tooltip_text(_("Create a new macro from the content currently available on the clipboard.")); + m_addFromSelectionButton.set_tooltip_text(_("Create a new macro from the currently selected dimension region's parameters currently shown on the main window.")); + m_addHBox.pack_start(m_addFromClipboardButton, Gtk::PACK_EXPAND_WIDGET/*, 15*/); + m_addHBox.pack_start(m_addFromSelectionButton, Gtk::PACK_EXPAND_WIDGET/*, 15*/); + m_vbox.pack_start(m_addHBox, Gtk::PACK_SHRINK); + + m_vbox.pack_start(m_mainHBox); + m_vbox.set_spacing(5); // create Macro list treeview (including its data model) m_treeStoreMacros = MacroListTreeStore::create(m_treeModelMacros); @@ -30,7 +63,7 @@ m_treeViewMacros.get_selection()->set_mode(Gtk::SELECTION_MULTIPLE); //m_treeViewMacro.set_tooltip_text(_("")); m_treeViewMacros.append_column(_("Key"), m_treeModelMacros.m_col_key); - m_treeViewMacros.append_column_editable(_("Name"), m_treeModelMacros.m_col_name); + m_treeViewMacros.append_column_editable(_("Macro Name"), m_treeModelMacros.m_col_name); m_treeViewMacros.append_column(_("Created"), m_treeModelMacros.m_col_created); m_treeViewMacros.append_column(_("Modified"), m_treeModelMacros.m_col_modified); m_treeViewMacros.set_tooltip_column(m_treeModelMacros.m_col_comment.index()); @@ -63,17 +96,40 @@ m_treeViewMacros.get_selection()->signal_changed().connect( sigc::mem_fun(*this, &MacrosSetup::onTreeViewSelectionChanged) ); - /*m_treeViewMacros.signal_key_release_event().connect_notify( + m_treeViewMacros.signal_key_release_event().connect_notify( sigc::mem_fun(*this, &MacrosSetup::onMacroTreeViewKeyRelease) - );*/ + ); m_treeStoreMacros->signal_row_changed().connect( sigc::mem_fun(*this, &MacrosSetup::onMacroTreeViewRowValueChanged) ); m_ignoreTreeViewValueChange = false; + m_ignoreCommentTextViewChange = false; m_scrolledWindow.add(m_treeViewMacros); m_scrolledWindow.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); - m_vbox.pack_start(m_scrolledWindow); + m_mainHBox.pack_start(m_scrolledWindow); + + m_rvbox.set_spacing(5); + + m_mainHBox.pack_start(m_rvbox, Gtk::PACK_SHRINK); + m_mainHBox.set_spacing(5), + m_rvbox.set_spacing(5); + m_rvbox.pack_start(m_detailsButtonBox, Gtk::PACK_SHRINK); + + //m_textViewComment.set_left_margin(3); + //m_textViewComment.set_right_margin(3); + m_textViewComment.set_indent(2); + m_scrolledWindowComment.add(m_textViewComment); + m_scrolledWindowComment.set_policy(Gtk::POLICY_AUTOMATIC, Gtk::POLICY_AUTOMATIC); + m_labelComment.set_markup( + "" + m_labelComment.get_text() + "" + ); + m_rvbox.pack_start(m_labelComment, Gtk::PACK_SHRINK); + m_rvbox.pack_start(m_scrolledWindowComment); + + m_detailsButtonBox.pack_start(m_buttonUp); + m_detailsButtonBox.pack_start(m_buttonDown); + m_detailsButtonBox.pack_start(m_buttonEdit); m_buttonBoxL.set_layout(Gtk::BUTTONBOX_START); m_buttonBoxL.pack_start(m_deleteButton); @@ -104,6 +160,30 @@ m_vbox.pack_start(m_footerHBox, Gtk::PACK_SHRINK); + m_addFromClipboardButton.signal_clicked().connect( + sigc::mem_fun(*this, &MacrosSetup::onButtonAddFromClipboard) + ); + + m_addFromSelectionButton.signal_clicked().connect( + sigc::mem_fun(*this, &MacrosSetup::onButtonAddFromSelection) + ); + + m_buttonUp.signal_clicked().connect( + sigc::mem_fun(*this, &MacrosSetup::onButtonUp) + ); + + m_buttonDown.signal_clicked().connect( + sigc::mem_fun(*this, &MacrosSetup::onButtonDown) + ); + + m_buttonEdit.signal_clicked().connect( + sigc::mem_fun(*this, &MacrosSetup::onButtonEdit) + ); + + m_textViewComment.get_buffer()->signal_changed().connect( + sigc::mem_fun(*this, &MacrosSetup::onCommentTextViewChanged) + ); + m_applyButton.signal_clicked().connect( sigc::mem_fun(*this, &MacrosSetup::onButtonApply) ); @@ -143,30 +223,165 @@ printf("MacrosSetup destruct\n"); } -void MacrosSetup::setMacros(const std::vector& macros) { +void MacrosSetup::setMacros(const std::vector& macros, + Serialization::Archive* pClipboardContent, + gig::DimensionRegion* pSelectedDimRgn) +{ // copy for non-destructive editing m_macros = macros; + m_clipboardContent = pClipboardContent; + m_selectedDimRgn = pSelectedDimRgn; + reloadTreeView(); } +void MacrosSetup::onButtonAddFromClipboard() { + printf("+fromClipboard\n"); + if (!m_clipboardContent) return; + if (!m_clipboardContent->rootObject()) return; + m_macros.push_back(*m_clipboardContent); + m_modified = true; + reloadTreeView(); +} + +void MacrosSetup::onButtonAddFromSelection() { + printf("+fromSelection\n"); + if (!m_selectedDimRgn) return; + std::string errorText; + try { + Serialization::Archive archive; + archive.serialize(m_selectedDimRgn); + //archive.setName("Unnamed Macro"); + m_macros.push_back(archive); + m_modified = true; + reloadTreeView(); + } catch (Serialization::Exception e) { + errorText = e.Message; + } catch (...) { + errorText = _("Unknown exception while creating macro"); + } + if (!errorText.empty()) { + Glib::ustring txt = _("Couldn't create macro:\n") + errorText; + Gtk::MessageDialog msg(*this, txt, false, Gtk::MESSAGE_ERROR); + msg.run(); + } +} + +void MacrosSetup::moveByDir(int d) { + if (d < -1 || d > 1) return; + int index = getSelectedMacroIndex(); + if (index < 0) return; + if (d == -1 && index == 0) return; + if (d == +1 && index >= m_macros.size() - 1) return; + + // swap macros + std::swap(m_macros[index + d], m_macros[index]); + + // swap tree view rows + Gtk::TreePath p1(ToString(index + d)); + Gtk::TreePath p2(ToString(index)); + Gtk::TreeModel::iterator it1 = m_treeStoreMacros->get_iter(p1); + Gtk::TreeModel::iterator it2 = m_treeStoreMacros->get_iter(p2); + m_treeStoreMacros->iter_swap(it1, it2); + int idx1 = (*it1)[m_treeModelMacros.m_col_index]; + int idx2 = (*it2)[m_treeModelMacros.m_col_index]; + (*it1)[m_treeModelMacros.m_col_index] = idx2; + (*it2)[m_treeModelMacros.m_col_index] = idx1; + Glib::ustring s1 = (*it1)[m_treeModelMacros.m_col_key]; + Glib::ustring s2 = (*it2)[m_treeModelMacros.m_col_key]; + (*it1)[m_treeModelMacros.m_col_key] = s2; + (*it2)[m_treeModelMacros.m_col_key] = s1; + + m_modified = true; +} + +void MacrosSetup::onButtonUp() { + moveByDir(-1); +} + +void MacrosSetup::onButtonDown() { + moveByDir(+1); +} + +void MacrosSetup::onButtonEdit() { + Serialization::Archive* macro = getSelectedMacro(); + if (!macro) return; + + m_modifiedBeforeMacroEditor = isModified(); + + MacroEditor* editor = new MacroEditor(); + editor->setMacro(macro, false); + editor->signal_changes_applied().connect( + sigc::mem_fun(*this, &MacrosSetup::onMacroEditorAppliedChanges) + ); + editor->show(); +} + +void MacrosSetup::onMacroEditorAppliedChanges() { + // so that the user does not need to click on a Apply buttons twice + if (!m_modifiedBeforeMacroEditor) + onButtonApply(); + updateStatus(); +} + +void MacrosSetup::onCommentTextViewChanged() { + if (m_ignoreCommentTextViewChange) return; + //printf("textChanged\n"); + Serialization::Archive* macro = getSelectedMacro(); + if (!macro) return; + macro->setComment( + m_textViewComment.get_buffer()->get_text() + ); + updateStatus(); +} + +int MacrosSetup::getSelectedMacroIndex() const { + std::vector v = m_treeViewMacros.get_selection()->get_selected_rows(); + if (v.empty()) return -1; + Gtk::TreeModel::iterator it = m_treeStoreMacros->get_iter(v[0]); + if (!it) return -1; + const Gtk::TreeModel::Row& row = *it; + int index = row[m_treeModelMacros.m_col_index]; + if (index < 0 || index >= m_macros.size()) return -1; + return index; +} + +Serialization::Archive* MacrosSetup::getSelectedMacro() { + int index = getSelectedMacroIndex(); + if (index < 0) return NULL; + return &m_macros[index]; +} + static Glib::ustring indexToAccKey(uint index) { if (index >= 12) return ""; return "F" + ToString(index+1); } +static int daysAgo(const tm& t) { + time_t now; + time(&now); + tm* pNow = localtime(&now); + if (!pNow) return 0; + if (pNow->tm_year == t.tm_year && + pNow->tm_mon == t.tm_mon && + pNow->tm_mday == t.tm_mday) return 0; + time_t past = mktime((tm*)&t); + return ceil(difftime(now, past) / 60.0 / 60.0 / 24.0); +} + static Glib::ustring humanShortStr(const tm& t) { + int iDaysAgo = daysAgo(t); char buf[70]; - int daysAgo; - if (daysAgo = 0) { + if (iDaysAgo == 0) { // C-Time specification for a time somewhere today (see 'man strftime()'). if (strftime(buf, sizeof buf, _("%R"), &t)) return buf; - } else if (daysAgo = 1) { + } else if (iDaysAgo == 1) { // C-Time specification for a time somewhere yesterday (see 'man strftime()'). if (strftime(buf, sizeof buf, _("Yesterday %R"), &t)) return buf; - } else if (daysAgo = 2) { + } else if (iDaysAgo == 2) { // C-Time specification for a time somewhere 2 days ago (see 'man strftime()'). if (strftime(buf, sizeof buf, _("2 days ago %R"), &t)) return buf; @@ -189,8 +404,8 @@ Gtk::TreeModel::iterator iter = m_treeStoreMacros->append(); Gtk::TreeModel::Row row = *iter; row[m_treeModelMacros.m_col_key] = indexToAccKey(iMacro); - row[m_treeModelMacros.m_col_name] = gig_to_utf8(macro.name()); - row[m_treeModelMacros.m_col_comment] = gig_to_utf8(macro.comment()); + row[m_treeModelMacros.m_col_name] = macro.name().empty() ? _("Unnamed Macro") : gig_to_utf8(macro.name()); + row[m_treeModelMacros.m_col_comment] = macro.comment().empty() ? _("No comment assigned yet.") : gig_to_utf8(macro.comment()); row[m_treeModelMacros.m_col_created] = humanShortStr(macro.dateTimeCreated()); row[m_treeModelMacros.m_col_modified] = humanShortStr(macro.dateTimeModified()); row[m_treeModelMacros.m_col_index] = iMacro; @@ -208,12 +423,40 @@ const bool bValidSelection = !v.empty(); m_deleteButton.set_sensitive(bValidSelection); m_inverseDeleteButton.set_sensitive(bValidSelection); -} + m_buttonEdit.set_sensitive(bValidSelection); + + // update comment text view + std::string sComment; + Serialization::Archive* macro = getSelectedMacro(); + if (macro) + sComment = macro->comment(); + m_ignoreCommentTextViewChange = true; + m_textViewComment.get_buffer()->set_text(sComment); + m_ignoreCommentTextViewChange = false; + m_textViewComment.set_sensitive(bValidSelection); +} + +// Cmd key on Mac, Ctrl key on all other OSs +static const guint primaryKeyL = + #if defined(__APPLE__) + GDK_KEY_Meta_L; + #else + GDK_KEY_Control_L; + #endif + +static const guint primaryKeyR = + #if defined(__APPLE__) + GDK_KEY_Meta_R; + #else + GDK_KEY_Control_R; + #endif bool MacrosSetup::onKeyPressed(GdkEventKey* key) { //printf("key down 0x%x\n", key->keyval); if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R) m_altKeyDown = true; + if (key->keyval == primaryKeyL || key->keyval == primaryKeyR) + m_primaryKeyDown = true; return false; } @@ -221,6 +464,12 @@ //printf("key up 0x%x\n", key->keyval); if (key->keyval == GDK_KEY_Alt_L || key->keyval == GDK_KEY_Alt_R) m_altKeyDown = false; + if (key->keyval == primaryKeyL || key->keyval == primaryKeyR) + m_primaryKeyDown = false; + if (m_primaryKeyDown && key->keyval == GDK_KEY_b) + onButtonAddFromClipboard(); + if (m_primaryKeyDown && key->keyval == GDK_KEY_s) + onButtonAddFromSelection(); return false; } @@ -228,7 +477,7 @@ if (key->keyval == GDK_KEY_BackSpace || key->keyval == GDK_KEY_Delete) { if (m_altKeyDown) inverseDeleteSelectedRows(); - else + else if (m_primaryKeyDown) deleteSelectedRows(); } } @@ -243,6 +492,8 @@ int index = row[m_treeModelMacros.m_col_index]; m_macros[index].setName(name); //reloadTreeView(); + m_modified = true; + updateStatus(); } void MacrosSetup::deleteSelectedRows() { @@ -252,6 +503,7 @@ } void MacrosSetup::deleteRows(const std::vector& rows) { + m_modified = !rows.empty(); std::set macros; for (int r = rows.size() - 1; r >= 0; --r) { Gtk::TreeModel::iterator it = m_treeStoreMacros->get_iter(rows[r]); @@ -298,7 +550,14 @@ } void MacrosSetup::updateStatus() { + bool bValidSelection = !m_treeViewMacros.get_selection()->get_selected_rows().empty(); + m_addFromClipboardButton.set_sensitive( + m_clipboardContent && m_clipboardContent->rootObject() + ); + m_addFromSelectionButton.set_sensitive(m_selectedDimRgn); + m_buttonEdit.set_sensitive(bValidSelection); m_applyButton.set_sensitive(isModified()); + m_textViewComment.set_sensitive(bValidSelection); updateStatusBar(); } @@ -320,7 +579,7 @@ //gchar* msg = g_strdup_printf(_("Apply changes to macro \"%s\" before closing?"), // m_macroOriginal->Name.c_str()); - gchar* msg = g_strdup_printf(_("Apply changes to macro before closing?")); + gchar* msg = g_strdup_printf(_("Apply changes to macro list before closing?")); Gtk::MessageDialog dialog(*this, msg, false, Gtk::MESSAGE_WARNING, Gtk::BUTTONS_NONE); g_free(msg); dialog.set_secondary_text(_("If you close without applying, your changes will be lost.")); @@ -352,6 +611,7 @@ } bool MacrosSetup::isModified() const { + if (m_modified) return true; bool bModified = false; for (int i = 0; i < m_macros.size(); ++i) { if (m_macros[i].isModified()) { @@ -377,6 +637,7 @@ // 'modified' state m_macros[i].rawData(); } + m_modified = false; } catch (Serialization::Exception e) { errorText = e.Message; } catch (...) {