diff --git a/src/ct/ct_actions.h b/src/ct/ct_actions.h index 90ab76f07..e81e33494 100644 --- a/src/ct/ct_actions.h +++ b/src/ct/ct_actions.h @@ -130,6 +130,7 @@ class CtActions bool _is_there_selected_node_or_error(); bool _is_tree_not_empty_or_error(); bool _is_curr_node_not_read_only_or_error(); + bool _is_curr_node_or_parent_not_locked_or_error(); bool _is_curr_node_not_syntax_highlighting_or_error(bool plain_text_ok = false); bool _is_there_text_selection_or_error(); @@ -178,6 +179,7 @@ class CtActions void node_inherit_syntax(); void node_delete(); void node_toggle_read_only(); + void node_toggle_lock(); void node_date(); void node_up(); void node_down(); diff --git a/src/ct/ct_actions_import.cc b/src/ct/ct_actions_import.cc index f858bb8a7..06d5da011 100644 --- a/src/ct/ct_actions_import.cc +++ b/src/ct/ct_actions_import.cc @@ -285,6 +285,7 @@ void CtActions::_create_imported_nodes(ct_imported_node* imported_nodes, const b node_data.nodeId = imported_node->node_id; node_data.isBold = false; node_data.customIconId = 0; + node_data.lockId = 0; node_data.isRO = false; node_data.syntax = imported_node->node_syntax; node_data.tsCreation = std::time(nullptr); diff --git a/src/ct/ct_actions_tree.cc b/src/ct/ct_actions_tree.cc index 55c66c4a5..3faf55e7f 100644 --- a/src/ct/ct_actions_tree.cc +++ b/src/ct/ct_actions_tree.cc @@ -45,12 +45,27 @@ bool CtActions::_is_tree_not_empty_or_error() return true; } +bool CtActions::_is_curr_node_or_parent_not_locked_or_error() +{ + CtTreeIter node; + if (_pCtMainWin->curr_tree_iter().get_is_node_or_parent_locked(node)) { + CtDialogs::error_dialog(str::format(_("The Node {} is Locked by {}"), node.get_node_name().c_str(), node.get_node_lock_id()), *_pCtMainWin); + return true; + } + return false; +} + bool CtActions::_is_curr_node_not_read_only_or_error() { if (_pCtMainWin->curr_tree_iter().get_node_read_only()) { CtDialogs::error_dialog(_("The Selected Node is Read Only"), *_pCtMainWin); return false; } + + if (_is_curr_node_or_parent_not_locked_or_error()) { + return false; + } + return true; } @@ -172,6 +187,7 @@ void CtActions::_node_add(bool duplicate, bool add_child) std::string title = add_child ? _("New Child Node Properties") : _("New Node Properties"); nodeData.isBold = false; nodeData.customIconId = 0; + nodeData.lockId = 0; nodeData.syntax = _pCtMainWin->curr_tree_iter() ? _pCtMainWin->curr_tree_iter().get_node_syntax_highlighting() : CtConst::RICH_TEXT_ID; nodeData.isRO = false; if (not CtDialogs::node_prop_dialog(title, _pCtMainWin, nodeData, _pCtMainWin->get_tree_store().get_used_tags())) @@ -220,6 +236,7 @@ void CtActions::node_child_exist_or_create(Gtk::TreeIter parentIter, const std:: nodeData.name = nodeName; nodeData.isBold = false; nodeData.customIconId = 0; + nodeData.lockId = 0; nodeData.syntax = CtConst::RICH_TEXT_ID; nodeData.isRO = false; _node_add_with_data(parentIter, nodeData, true, nullptr); @@ -447,6 +464,7 @@ void CtActions::node_delete() void CtActions::node_toggle_read_only() { if (!_is_there_selected_node_or_error()) return; + if (_is_curr_node_or_parent_not_locked_or_error()) return; bool node_is_ro = !_pCtMainWin->curr_tree_iter().get_node_read_only(); _pCtMainWin->curr_tree_iter().set_node_read_only(node_is_ro); _pCtMainWin->get_text_view().set_editable(!node_is_ro); @@ -457,6 +475,19 @@ void CtActions::node_toggle_read_only() _pCtMainWin->get_text_view().grab_focus(); } +void CtActions::node_toggle_lock() +{ + if (!_is_there_selected_node_or_error()) return; + if (_is_curr_node_or_parent_not_locked_or_error()) return; + guint32 node_lock_id = _pCtMainWin->curr_tree_iter().get_node_lock_id(); + node_lock_id = node_lock_id > 0u ? 0u : (guint32) _pCtMainWin->get_ct_config()->userLockId; + _pCtMainWin->curr_tree_iter().set_node_lock_id(node_lock_id); + _pCtMainWin->update_selected_node_statusbar_info(); + _pCtMainWin->get_tree_store().update_node_aux_icon(_pCtMainWin->curr_tree_iter()); + _pCtMainWin->update_window_save_needed(CtSaveNeededUpdType::npro); + _pCtMainWin->get_text_view().grab_focus(); +} + void CtActions::node_date() { time_t time = std::time(nullptr); diff --git a/src/ct/ct_config.cc b/src/ct/ct_config.cc index 38bf12856..7b3204952 100644 --- a/src/ct/ct_config.cc +++ b/src/ct/ct_config.cc @@ -346,6 +346,7 @@ void CtConfig::_populate_keyfile_from_data() _uKeyFile->set_boolean(_currentGroup, "enable_custom_backup_dir", customBackupDirOn); _uKeyFile->set_string(_currentGroup, "custom_backup_dir", customBackupDir); _uKeyFile->set_integer(_currentGroup, "limit_undoable_steps", limitUndoableSteps); + _uKeyFile->set_integer(_currentGroup, "user_lock_id", userLockId); // [keyboard] _currentGroup = "keyboard"; @@ -643,6 +644,8 @@ void CtConfig::_populate_data_from_keyfile() _populate_bool_from_keyfile("enable_custom_backup_dir", &customBackupDirOn); _populate_string_from_keyfile("custom_backup_dir", &customBackupDir); _populate_int_from_keyfile("limit_undoable_steps", &limitUndoableSteps); + _populate_int_from_keyfile("user_lock_id", &userLockId); + userLockId = _build_lock_id(userLockId); // [keyboard] _currentGroup = "keyboard"; @@ -661,6 +664,17 @@ void CtConfig::_populate_data_from_keyfile() _populate_map_from_current_group(&customCodexecExt); } +int CtConfig::_build_lock_id(const int id) +{ + if (0 == userLockId) { + auto duration = std::chrono::system_clock::now().time_since_epoch(); + auto millis = std::chrono::duration_cast(duration).count(); + srand(millis); + return rand(); + } + return id; +} + void CtConfig::_ensure_user_styles_exist() { for (unsigned n = 1; n <= CtConst::NUM_USER_STYLES; ++n) { diff --git a/src/ct/ct_config.h b/src/ct/ct_config.h index 20f395ae6..5b22942bd 100644 --- a/src/ct/ct_config.h +++ b/src/ct/ct_config.h @@ -204,6 +204,7 @@ class CtConfig bool customBackupDirOn{false}; std::string customBackupDir{""}; int limitUndoableSteps{20}; + int userLockId{0}; bool usePandoc{true}; // Whether to use Pandoc for exporting // [keyboard] @@ -242,6 +243,7 @@ class CtConfig void _populate_keyfile_from_data(); void _unexpected_keyfile_error(const gchar* key, const Glib::KeyFileError& kferror); + int _build_lock_id(const int id); void _ensure_user_styles_exist(); static const size_t _maxTempKeySize{20}; diff --git a/src/ct/ct_menu.cc b/src/ct/ct_menu.cc index b09e6adc5..e2d3ec4d2 100644 --- a/src/ct/ct_menu.cc +++ b/src/ct/ct_menu.cc @@ -164,6 +164,7 @@ void CtMenu::init_actions(CtActions* pActions) _actions.push_back(CtMenuAction{tree_cat, "tree_dup_node_subnodes", "ct_tree-nodesub-dupl", _("_Duplicate Node and Sub Nodes"), None, _("Duplicate the Selected Node With SubNodes"), sigc::mem_fun(*pActions, &CtActions::node_subnodes_duplicate)}); _actions.push_back(CtMenuAction{tree_cat, "tree_node_prop", "ct_cherry_edit", _("Change Node _Properties"), "F2", _("Edit the Properties of the Selected Node"), sigc::mem_fun(*pActions, &CtActions::node_edit)}); _actions.push_back(CtMenuAction{tree_cat, "tree_node_toggle_ro", "ct_locked", _("Toggle _Read Only"), KB_CONTROL+KB_ALT+"R", _("Toggle the Read Only Property of the Selected Node"), sigc::mem_fun(*pActions, &CtActions::node_toggle_read_only)}); + _actions.push_back(CtMenuAction{tree_cat, "tree_node_toggle_lock", "ct_locked", _("Lock"), KB_CONTROL+KB_ALT+"L", _("Acquire/Release Lock on the Selected Node"), sigc::mem_fun(*pActions, &CtActions::node_toggle_lock)}); _actions.push_back(CtMenuAction{tree_cat, "tree_node_date", "ct_calendar", _("Insert Today's Node"), "F8", _("Insert a Node with Hierarchy Year/Month/Day"), sigc::mem_fun(*pActions, &CtActions::node_date)}); _actions.push_back(CtMenuAction{tree_cat, "tree_parse_info", "ct_info", _("Tree _Info"), None, _("Tree Summary Information"), sigc::mem_fun(*pActions, &CtActions::tree_info)}); _actions.push_back(CtMenuAction{tree_cat, "tree_node_up", "ct_go-up", _("Node _Up"), KB_SHIFT+KB_ALT+CtConst::STR_KEY_UP, _("Move the Selected Node Up"), sigc::mem_fun(*pActions, &CtActions::node_up)}); @@ -844,6 +845,7 @@ const char* CtMenu::_get_ui_str_menu() + diff --git a/src/ct/ct_storage_sqlite.cc b/src/ct/ct_storage_sqlite.cc index b5a3616c2..d2ac83ae2 100644 --- a/src/ct/ct_storage_sqlite.cc +++ b/src/ct/ct_storage_sqlite.cc @@ -403,7 +403,8 @@ Gtk::TreeIter CtStorageSqlite::_node_from_db(gint64 node_id, gint64 sequence, Gt nodeData.tags = safe_sqlite3_column_text(*uStmt, 2); gint64 readonly_n_custom_icon_id = sqlite3_column_int64(*uStmt, 3); nodeData.isRO = static_cast(readonly_n_custom_icon_id & 0x01); - nodeData.customIconId = readonly_n_custom_icon_id >> 1; + nodeData.customIconId = (readonly_n_custom_icon_id >> 1) & 0xffff; + nodeData.lockId = readonly_n_custom_icon_id >> 17; gint64 richtxt_bold_foreground = sqlite3_column_int64(*uStmt, 4); nodeData.isBold = static_cast((richtxt_bold_foreground >> 1) & 0x01); nodeData.sequence = sequence; @@ -622,6 +623,7 @@ void CtStorageSqlite::_write_node_to_db(CtTreeIter* ct_tree_iter, // is_ro is packed with additional bitfield data gint64 is_ro = ct_tree_iter->get_node_read_only() ? 0x01 : 0x00; is_ro |= ct_tree_iter->get_node_custom_icon_id() << 1; + is_ro |= ((gint64)ct_tree_iter->get_node_lock_id()) << 17; // is_richtxt is packed with additional bitfield data gint64 is_richtxt = ct_tree_iter->get_node_is_rich_text() ? 0x01 : 0x00; if (ct_tree_iter->get_node_is_bold()) diff --git a/src/ct/ct_storage_xml.cc b/src/ct/ct_storage_xml.cc index c8f5624b0..8ac865fa0 100644 --- a/src/ct/ct_storage_xml.cc +++ b/src/ct/ct_storage_xml.cc @@ -196,6 +196,7 @@ Gtk::TreeIter CtStorageXml::_node_from_xml(xmlpp::Element* xml_element, gint64 s node_data.tags = xml_element->get_attribute_value("tags"); node_data.isRO = CtStrUtil::is_str_true(xml_element->get_attribute_value("readonly")); node_data.customIconId = (guint32)CtStrUtil::gint64_from_gstring(xml_element->get_attribute_value("custom_icon_id").c_str()); + node_data.lockId = (guint32)CtStrUtil::gint64_from_gstring(xml_element->get_attribute_value("lock_id").c_str()); node_data.isBold = CtStrUtil::is_str_true(xml_element->get_attribute_value("is_bold")); node_data.foregroundRgb24 = xml_element->get_attribute_value("foreground"); node_data.tsCreation = CtStrUtil::gint64_from_gstring(xml_element->get_attribute_value("ts_creation").c_str()); @@ -304,6 +305,7 @@ xmlpp::Element* CtStorageXmlHelper::node_to_xml(CtTreeIter* ct_tree_iter, p_node_node->set_attribute("tags", ct_tree_iter->get_node_tags()); p_node_node->set_attribute("readonly", std::to_string(ct_tree_iter->get_node_read_only())); p_node_node->set_attribute("custom_icon_id", std::to_string(ct_tree_iter->get_node_custom_icon_id())); + p_node_node->set_attribute("lock_id", std::to_string(ct_tree_iter->get_node_lock_id())); p_node_node->set_attribute("is_bold", std::to_string(ct_tree_iter->get_node_is_bold())); p_node_node->set_attribute("foreground", ct_tree_iter->get_node_foreground()); p_node_node->set_attribute("ts_creation", std::to_string(ct_tree_iter->get_node_creating_time())); diff --git a/src/ct/ct_treestore.cc b/src/ct/ct_treestore.cc index a02de529d..28988d509 100644 --- a/src/ct/ct_treestore.cc +++ b/src/ct/ct_treestore.cc @@ -99,6 +99,16 @@ guint16 CtTreeIter::get_node_custom_icon_id() const return (*this) ? (*this)->get_value(_pColumns->colCustomIconId) : 0; } +guint32 CtTreeIter::get_node_lock_id() const +{ + return (*this) ? (*this)->get_value(_pColumns->colNodeLockId) : 0; +} + +void CtTreeIter::set_node_lock_id(const guint32 node_lock_id) +{ + (*this)->set_value(_pColumns->colNodeLockId, node_lock_id); +} + Glib::ustring CtTreeIter::get_node_name() const { return (*this) ? (*this)->get_value(_pColumns->colNodeName) : ""; @@ -186,6 +196,19 @@ Glib::RefPtr CtTreeIter::get_node_text_buffer() const return rRetTextBuffer; } +bool CtTreeIter::get_is_node_or_parent_locked(CtTreeIter& node) const +{ + CtTreeIter iter = *this; + while (iter != nullptr) { + if ((iter.get_node_lock_id() > 0) && (iter.get_node_lock_id() != (guint32) _pCtMainWin->get_ct_config()->userLockId)) { + node = iter; + return true; + } + iter = iter.parent(); + } + return false; +} + bool CtTreeIter::get_node_buffer_already_loaded() const { return static_cast((*this)->get_value(_pColumns->rColTextBuffer)); @@ -492,7 +515,8 @@ void CtTreeStore::text_view_apply_textbuffer(CtTreeIter& treeIter, CtTextView* p pTextView->set_buffer(rTextBuffer); pTextView->set_spell_check(treeIter.get_node_is_rich_text()); pTextView->set_sensitive(true); - pTextView->set_editable(not treeIter.get_node_read_only()); + CtTreeIter locked_node; + pTextView->set_editable((not treeIter.get_node_read_only()) && (not treeIter.get_is_node_or_parent_locked(locked_node))); for (CtAnchoredWidget* pCtAnchoredWidget : treeIter.get_anchored_widgets_fast()) { @@ -587,6 +611,7 @@ void CtTreeStore::get_node_data(const Gtk::TreeIter& treeIter, CtNodeData& nodeD nodeData.isRO = row[_columns.colNodeRO]; //row[_columns.rColPixbufAux] = ; nodeData.customIconId = row[_columns.colCustomIconId]; + nodeData.lockId = row[_columns.colNodeLockId]; nodeData.isBold = CtTreeIter::get_is_bold_from_pango_weight(row[_columns.colWeight]); nodeData.foregroundRgb24 = row[_columns.colForeground]; nodeData.tsCreation = row[_columns.colTsCreation]; @@ -607,6 +632,7 @@ void CtTreeStore::update_node_data(const Gtk::TreeIter& treeIter, const CtNodeDa row[_columns.colNodeRO] = nodeData.isRO; //row[_columns.rColPixbufAux] = ; // will be updated by update_node_aux_icon row[_columns.colCustomIconId] = (guint16)nodeData.customIconId; + row[_columns.colNodeLockId] = nodeData.lockId; row[_columns.colWeight] = CtTreeIter::get_pango_weight_from_is_bold(nodeData.isBold); row[_columns.colForeground] = nodeData.foregroundRgb24; row[_columns.colTsCreation] = nodeData.tsCreation; diff --git a/src/ct/ct_treestore.h b/src/ct/ct_treestore.h index ffdf61b79..c42f6eed0 100644 --- a/src/ct/ct_treestore.h +++ b/src/ct/ct_treestore.h @@ -41,6 +41,7 @@ struct CtNodeData Glib::ustring tags; bool isRO{false}; guint32 customIconId{0}; + guint32 lockId{0}; bool isBold{false}; std::string foregroundRgb24; gint64 tsCreation{0}; @@ -57,7 +58,7 @@ class CtTreeModelColumns final : public Gtk::TreeModel::ColumnRecord { add(rColPixbuf); add(colNodeName); add(rColTextBuffer); add(colNodeUniqueId); add(colSyntaxHighlighting); add(colNodeSequence); add(colNodeTags); add(colNodeRO); - add(rColPixbufAux); add(colCustomIconId); add(colWeight); add(colForeground); + add(rColPixbufAux); add(colCustomIconId); add(colNodeLockId); add(colWeight); add(colForeground); add(colTsCreation); add(colTsLastSave); add(colAnchoredWidgets); } ~CtTreeModelColumns() final {} @@ -71,6 +72,7 @@ class CtTreeModelColumns final : public Gtk::TreeModel::ColumnRecord Gtk::TreeModelColumn colNodeRO; Gtk::TreeModelColumn> rColPixbufAux; Gtk::TreeModelColumn colCustomIconId; + Gtk::TreeModelColumn colNodeLockId; Gtk::TreeModelColumn colWeight; Gtk::TreeModelColumn colForeground; Gtk::TreeModelColumn colTsCreation; @@ -97,6 +99,8 @@ class CtTreeIter : public Gtk::TreeIter void set_node_id(const gint64 new_id); std::vector get_children_node_ids() const; guint16 get_node_custom_icon_id() const; + guint32 get_node_lock_id() const; + void set_node_lock_id(guint32 node_lock_id); Glib::ustring get_node_name() const; void set_node_name(const Glib::ustring& node_name); Glib::ustring get_node_tags() const; @@ -112,6 +116,7 @@ class CtTreeIter : public Gtk::TreeIter void set_node_text_buffer(Glib::RefPtr new_buffer, const std::string& new_syntax_hilighting); Glib::RefPtr get_node_text_buffer() const; bool get_node_buffer_already_loaded() const; + bool get_is_node_or_parent_locked(CtTreeIter& node) const; void remove_all_embedded_widgets(); std::list get_anchored_widgets_fast(const char doSort = 'n');