diff --git a/.gitignore b/.gitignore
index 94053f29..6ba47e7c 100644
--- a/.gitignore
+++ b/.gitignore
@@ -1 +1,6 @@
obj/*
+moc/*
+Makefile
+adtool
+.qmake.stash
+*.pro.user
diff --git a/Makefile b/Makefile
deleted file mode 100644
index 4511864e..00000000
--- a/Makefile
+++ /dev/null
@@ -1,31 +0,0 @@
-
-EXE := adtool
-SRCDIR := src
-OBJDIR := obj
-
-SOURCES += $(shell find $(SRCDIR)/ -name '*.c')
-OBJS := $(addprefix $(OBJDIR)/, $(addsuffix .o, $(basename $(notdir $(SOURCES)))))
-
-CFLAGS += -Isrc/libs/
-CFLAGS += -g -Wall -Wformat
-CFLAGS += `pkg-config --cflags gtk+-3.0`
-CFLAGS += -rdynamic
-LIBS += `pkg-config --libs gtk+-3.0`
-LIBS += -lldap -llber -lresolv -lgsasl
-
-$(shell mkdir -p $(OBJDIR))
-
-$(OBJDIR)/%.o: $(SRCDIR)/%.c
- $(CC) $(CFLAGS) -c -o $@ $<
-
-$(OBJDIR)/%.o: $(SRCDIR)/libs/%.c
- $(CC) $(CFLAGS) -c -o $@ $<
-
-all: $(EXE)
- @echo Build complete
-
-$(EXE): $(OBJS)
- $(CC) -o $@ $^ $(CFLAGS) $(LIBS)
-
-clean:
- rm -f $(EXE) $(OBJS)
diff --git a/README.md b/README.md
index 3bfff260..4e5bb71c 100644
--- a/README.md
+++ b/README.md
@@ -7,5 +7,5 @@ Requirements:
* `libkrb5-devel`
* `libgsasl-devel`
-
Run as `./adtool fake` to load fake entries and test without LDAP connection.
+
diff --git a/adtool.pro b/adtool.pro
new file mode 100644
index 00000000..ec9f1ad5
--- /dev/null
+++ b/adtool.pro
@@ -0,0 +1,20 @@
+QT += core gui widgets
+CONFIG += c++11
+DEFINES += QT_DEPRECATED_WARNINGS
+
+LIBS += -lldap -llber -lresolv -lgsasl
+
+SOURCES = src/*.cpp
+HEADERS = src/*.h
+
+SOURCES += src/active_directory.c
+
+DESTDIR = .
+OBJECTS_DIR = obj
+UI_DIR = src/ui
+MOC_DIR = moc
+
+FORMS += \
+ data/ui/mainwindow.ui
+
+
diff --git a/data/adtool.glade b/data/adtool.glade
deleted file mode 100644
index 7ba0a131..00000000
--- a/data/adtool.glade
+++ /dev/null
@@ -1,490 +0,0 @@
-
-
-
-
-
-
-
-
-
-
-
-
-
- False
- dialog
-
-
-
-
-
- False
- vertical
- 2
-
-
- False
- end
-
-
- gtk-ok
- True
- True
- True
- True
-
-
- True
- True
- 0
-
-
-
-
- gtk-cancel
- True
- True
- True
- True
-
-
- True
- True
- 1
-
-
-
-
- False
- False
- 0
-
-
-
-
- True
- False
-
-
- True
- False
- Name
-
-
- False
- True
- 0
-
-
-
-
- True
- True
- name
-
-
- False
- True
- 1
-
-
-
-
- False
- True
- 0
-
-
-
-
-
- button1
- button2
-
-
-
diff --git a/data/ui/mainwindow.ui b/data/ui/mainwindow.ui
new file mode 100644
index 00000000..4ea27ab1
--- /dev/null
+++ b/data/ui/mainwindow.ui
@@ -0,0 +1,161 @@
+
+
+ MainWindow
+
+
+
+ 0
+ 0
+ 1307
+ 637
+
+
+
+ MainWindow
+
+
+
+
+
+ 0
+ 0
+ 1301
+ 591
+
+
+
+ Qt::Horizontal
+
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+ true
+
+
+
+
+ QAbstractItemView::NoEditTriggers
+
+
+ QAbstractItemView::SingleSelection
+
+
+ false
+
+
+ false
+
+
+ false
+
+
+ true
+
+
+
+
+ QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed
+
+
+ QAbstractItemView::NoSelection
+
+
+ QAbstractItemView::SelectRows
+
+
+
+
+
+
+
+
+ What
+
+
+
+
+ Second
+
+
+
+
+ Something
+
+
+
+
+ Here
+
+
+
+
+ true
+
+
+ Advanced view
+
+
+
+
+
+ ContainersView
+ QTreeView
+
+
+
+ ContentsView
+ QTreeView
+
+
+
+ AttributesView
+ QTreeView
+
+
+
+
+
+
diff --git a/src/libs/active_directory.c b/src/active_directory.c
similarity index 100%
rename from src/libs/active_directory.c
rename to src/active_directory.c
diff --git a/src/libs/active_directory.h b/src/active_directory.h
similarity index 100%
rename from src/libs/active_directory.h
rename to src/active_directory.h
diff --git a/src/ad_filter.cpp b/src/ad_filter.cpp
new file mode 100644
index 00000000..6a14f151
--- /dev/null
+++ b/src/ad_filter.cpp
@@ -0,0 +1,37 @@
+
+#include "ad_filter.h"
+#include "ad_model.h"
+
+#include
+
+AdFilter::AdFilter(const QAction * const advanced_view_action, bool only_show_containers) {
+ this->only_show_containers = only_show_containers;
+
+ // On advanced view toggle, copy advanced view flag and invalidate filter
+ connect(advanced_view_action, &QAction::toggled,
+ [this](bool checked)
+ {
+ this->advanced_view = checked;
+ this->invalidateFilter();
+ });
+}
+
+bool AdFilter::filterAcceptsRow(int source_row, const QModelIndex &source_parent) const {
+ auto index = sourceModel()->index(source_row, 0, source_parent);
+
+ // Hide advanced view only entries if advanced view is OFF
+ auto advanced_view_only = index.data(AdModel::Roles::AdvancedViewOnly).toBool();
+ if (advanced_view_only && !this->advanced_view) {
+ return false;
+ }
+
+ if (only_show_containers) {
+ // Hide non-containers
+ auto is_container = index.data(AdModel::Roles::IsContainer).toBool();
+ if (!is_container) {
+ return false;
+ }
+ }
+
+ return true;
+}
diff --git a/src/ad_filter.h b/src/ad_filter.h
new file mode 100644
index 00000000..8cb2e0ec
--- /dev/null
+++ b/src/ad_filter.h
@@ -0,0 +1,22 @@
+
+#pragma once
+
+#include
+
+class QModelIndex;
+class QAction;
+class AdModel;
+
+// TODO: only allow AdModel source models
+// Filter out advanced entries when advanced view is off
+// Connected to advanced view toggle in menubar
+class AdFilter : public QSortFilterProxyModel {
+public:
+ explicit AdFilter(const QAction * const advanced_view_action, bool only_show_containers = false);
+
+private:
+ bool advanced_view = false;
+ bool only_show_containers;
+
+ bool filterAcceptsRow(int source_row, const QModelIndex &source_parent) const override;
+};
diff --git a/src/ad_interface.cpp b/src/ad_interface.cpp
new file mode 100644
index 00000000..dd2e6958
--- /dev/null
+++ b/src/ad_interface.cpp
@@ -0,0 +1,183 @@
+
+#include "ad_interface.h"
+#include "constants.h"
+
+extern "C" {
+#include "active_directory.h"
+}
+
+// TODO: replace C active_directory.h with C++ version
+
+// -----------------------------------------------------------------
+// FAKE STUFF
+// -----------------------------------------------------------------
+
+bool FAKE_AD = false;
+
+QMap> fake_children;
+QMap>> fake_attributes;
+
+void fake_ad_init() {
+ fake_children[HEAD_DN] = {
+ QString("CN=A,") + HEAD_DN,
+ QString("CN=B,") + HEAD_DN,
+ QString("CN=C,") + HEAD_DN,
+ QString("CN=D,") + HEAD_DN,
+ };
+
+ fake_attributes[HEAD_DN] = {
+ {"name", {"domain"}},
+ {"objectClass", {"container"}},
+ {"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
+ {"showInAdvancedViewOnly", {"FALSE"}},
+ };
+
+ fake_attributes[QString("CN=A,") + HEAD_DN] = {
+ {"name", {"A"}},
+ {"objectClass", {"container"}},
+ {"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
+ {"showInAdvancedViewOnly", {"FALSE"}},
+ {"description", {"A's description"}},
+ };
+
+ fake_attributes[QString("CN=B,") + HEAD_DN] = {
+ {"name", {"B"}},
+ {"objectClass", {"container"}},
+ {"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
+ {"showInAdvancedViewOnly", {"FALSE"}},
+ {"description", {"B's description"}},
+ };
+
+ fake_attributes[QString("CN=C,") + HEAD_DN] = {
+ {"name", {"C"}},
+ {"objectClass", {"container"}},
+ {"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
+ {"showInAdvancedViewOnly", {"FALSE"}},
+ };
+
+ fake_attributes[QString("CN=D,") + HEAD_DN] = {
+ {"name", {"D"}},
+ {"objectClass", {"container"}},
+ {"objectCategory", {"CN=Container,CN=Schema,CN=Configuration"}},
+ {"showInAdvancedViewOnly", {"TRUE"}},
+ };
+
+ fake_children[QString("CN=A,") + HEAD_DN] = {
+ QString("CN=B's child,CN=B,") + HEAD_DN
+ };
+ fake_attributes[QString("CN=B's child,CN=B,") + HEAD_DN] = {
+ {"name", {"B's child"}},
+ {"objectClass", {"person"}},
+ {"objectCategory", {"CN=Person,CN=Schema,CN=Configuration"}},
+ {"showInAdvancedViewOnly", {"FALSE"}},
+ };
+}
+
+QList fake_load_children(QString &dn) {
+ if (!fake_children.contains(dn)) {
+ // NOTE: ok to have empty children for leaves
+ fake_children[dn] = QList();
+ }
+
+ return fake_children[dn];
+}
+
+QMap> fake_load_attributes(QString &dn) {
+ if (!fake_attributes.contains(dn)) {
+ printf("load_attributes failed for %s, loading empty attributes\n", qPrintable(dn));
+ fake_attributes[dn] = QMap>();
+ }
+
+ return fake_attributes[dn];
+}
+
+// -----------------------------------------------------------------
+// REAL STUFF
+// -----------------------------------------------------------------
+
+
+bool ad_interface_login() {
+ if (FAKE_AD) {
+ fake_ad_init();
+ return true;
+ }
+
+ LDAP* ldap_connection = ad_login();
+ if (ldap_connection == NULL) {
+ printf("ad_login error: %s\n", ad_get_error());
+ return false;
+ } else {
+ return true;
+ }
+}
+
+QList load_children(QString &dn) {
+ if (FAKE_AD) {
+ return fake_load_children(dn);
+ }
+
+ char **children_raw = ad_list(dn.toLatin1().data());
+
+ // TODO: error check
+
+ if (children_raw != NULL) {
+ auto children = QList();
+
+ for (int i = 0; children_raw[i] != NULL; i++) {
+ auto child = QString(children_raw[i]);
+ children.push_back(child);
+ }
+
+ for (int i = 0; children_raw[i] != NULL; i++) {
+ free(children_raw[i]);
+ }
+ free(children_raw);
+
+ return children;
+ } else {
+ return QList();
+ }
+}
+
+QMap> load_attributes(QString &dn) {
+ if (FAKE_AD) {
+ return fake_load_attributes(dn);
+ }
+
+ // TODO: save original attributes ordering and load it like that into model
+
+ char** attributes_raw = ad_get_attribute(dn.toLatin1().data(), NULL);
+
+ // TODO: error check
+
+ if (attributes_raw != NULL) {
+ auto attributes = QMap>();
+
+ // Load attributes map
+ // attributes_raw is in the form of:
+ // char** array of {key, value, value, key, value ...}
+ // transform it into:
+ // map of {key => {value, value ...}, key => {value, value ...} ...}
+ for (int i = 0; attributes_raw[i + 2] != NULL; i += 2) {
+ auto attribute = QString(attributes_raw[i]);
+ auto value = QString(attributes_raw[i + 1]);
+
+ // Make values list if doesn't exist yet
+ if (!attributes.contains(attribute)) {
+ attributes[attribute] = QList();
+ }
+
+ attributes[attribute].push_back(value);
+ }
+
+ // Free attributes_raw
+ for (int i = 0; attributes_raw[i] != NULL; i++) {
+ free(attributes_raw[i]);
+ }
+ free(attributes_raw);
+
+ return attributes;
+ } else {
+ return QMap>();
+ }
+}
diff --git a/src/ad_interface.h b/src/ad_interface.h
new file mode 100644
index 00000000..b9739a36
--- /dev/null
+++ b/src/ad_interface.h
@@ -0,0 +1,15 @@
+
+#pragma once
+
+#include
+#include
+#include
+
+// Interface functions to convert from raw char** active directory returns to Qt containers
+// Also can load fake data if program is run with "fake" option
+
+extern bool FAKE_AD;
+
+bool ad_interface_login();
+QList load_children(QString &dn);
+QMap> load_attributes(QString &dn);
diff --git a/src/ad_model.cpp b/src/ad_model.cpp
new file mode 100644
index 00000000..fbd6f8ed
--- /dev/null
+++ b/src/ad_model.cpp
@@ -0,0 +1,134 @@
+
+#include "ad_model.h"
+#include "ad_interface.h"
+#include "constants.h"
+
+#include
+
+QList make_row(QString dn) {
+ auto attributes = load_attributes(dn);
+
+ // TODO: get rid of "if (x.contains(y))"
+
+ // Name
+ auto name = dn;
+ if (attributes.contains("name")) {
+ name = attributes["name"][0];
+ }
+
+ // Category
+ auto category = dn;
+ if (attributes.contains("objectCategory")) {
+ // NOTE: raw category is given as DN
+ // TODO: convert it completely (turn '-' into ' ')
+ auto category_as_dn = attributes["objectCategory"][0];
+ auto equals_index = category_as_dn.indexOf('=');
+ auto comma_index = category_as_dn.indexOf(',');
+ auto segment_length = comma_index - equals_index;
+ // TODO: check what happens if equals is negative
+ category = category_as_dn.mid(equals_index + 1, segment_length);
+ }
+
+ // Description
+ QString description = "none";
+ if (attributes.contains("description")) {
+ description = attributes["description"][0];
+ }
+
+ // showInAdvancedViewOnly
+ bool advanced_view = false;
+ if (attributes.contains("showInAdvancedViewOnly")) {
+ auto advanced_view_str = attributes["showInAdvancedViewOnly"][0];
+
+ if (advanced_view_str == "TRUE") {
+ advanced_view = true;
+ }
+ }
+
+ // is container
+ bool is_container = false;
+ if (attributes.contains("objectClass")) {
+ auto objectClasses = attributes["objectClass"];
+
+ QList container_objectClasses = {"container", "organizationalUnit", "builtinDomain", "domain"};
+ for (auto e : container_objectClasses) {
+ if (objectClasses.contains(e)) {
+ is_container = true;
+ break;
+ }
+ }
+ }
+
+ QList row = {
+ new QStandardItem(name),
+ new QStandardItem(category),
+ new QStandardItem(description),
+ };
+
+ // NOTE: store special invisible attributes in Roles of first item
+ // TODO: shouldn't store these in roles
+ row[0]->setData(dn, AdModel::Roles::DN);
+ row[0]->setData(advanced_view, AdModel::Roles::AdvancedViewOnly);
+ row[0]->setData(true, AdModel::Roles::CanFetch);
+ row[0]->setData(is_container, AdModel::Roles::IsContainer);
+
+ return row;
+}
+
+AdModel::AdModel(): QStandardItemModel(0, Column::COUNT) {
+ this->setHorizontalHeaderItem(Column::Name, new QStandardItem("Name"));
+ this->setHorizontalHeaderItem(Column::Category, new QStandardItem("Category"));
+ this->setHorizontalHeaderItem(Column::Description, new QStandardItem("Description"));
+
+ // Load head
+ auto invis_root = this->invisibleRootItem();
+ auto head_row = make_row(HEAD_DN);
+ invis_root->appendRow(head_row);
+}
+
+bool AdModel::canFetchMore(const QModelIndex &parent) const {
+ if (!parent.isValid()) {
+ return false;
+ }
+
+ auto dn = parent.data(Roles::DN).toString();
+ // printf("canFetchMore: %s\n", qPrintable(dn));
+
+ auto can_fetch = parent.data(AdModel::Roles::CanFetch).toBool();
+
+ return can_fetch;
+}
+
+void AdModel::fetchMore(const QModelIndex &parent) {
+ if (!parent.isValid() || !this->canFetchMore(parent)) {
+ return;
+ }
+
+ auto dn = parent.data(Roles::DN).toString();
+
+ auto parent_item = this->itemFromIndex(parent);
+
+ // Add children
+ auto children = load_children(dn);
+
+ for (auto child : children) {
+ auto child_row = make_row(child);
+
+ parent_item->appendRow(child_row);
+ }
+
+ // Unset CanFetch flag
+ parent_item->setData(false, AdModel::Roles::CanFetch);
+
+ // printf("fetchMore: %s\n", qPrintable(dn));
+}
+
+// Override this so that unexpanded and unfetched items show the expander even though they technically don't have any children loaded
+// NOTE: expander is show if hasChildren returns true
+bool AdModel::hasChildren(const QModelIndex &parent = QModelIndex()) const {
+ if (this->canFetchMore(parent)) {
+ return true;
+ } else {
+ return QStandardItemModel::hasChildren(parent);
+ }
+}
diff --git a/src/ad_model.h b/src/ad_model.h
new file mode 100644
index 00000000..3f7570b4
--- /dev/null
+++ b/src/ad_model.h
@@ -0,0 +1,31 @@
+#pragma once
+
+#include
+
+class AdModel: public QStandardItemModel {
+Q_OBJECT
+
+public:
+ enum Column {
+ Name,
+ Category,
+ Description,
+ COUNT,
+ };
+
+ explicit AdModel();
+
+ enum Roles {
+ DN = Qt::UserRole + 1,
+ AdvancedViewOnly = Qt::UserRole + 2,
+ CanFetch = Qt::UserRole + 3,
+ IsContainer = Qt::UserRole + 4,
+ };
+
+ bool canFetchMore(const QModelIndex &parent) const;
+ void fetchMore(const QModelIndex &parent);
+ bool hasChildren(const QModelIndex &parent) const override;
+
+private:
+
+};
diff --git a/src/attributes_model.cpp b/src/attributes_model.cpp
new file mode 100644
index 00000000..340e3260
--- /dev/null
+++ b/src/attributes_model.cpp
@@ -0,0 +1,18 @@
+
+#include "attributes_model.h"
+
+AttributesModel::AttributesModel(): QStandardItemModel(0, Column::COUNT) {
+ this->setHorizontalHeaderItem(Column::Name, new QStandardItem("Name"));
+ this->setHorizontalHeaderItem(Column::Value, new QStandardItem("Value"));
+}
+
+// This will be called when an attribute value is edited
+bool AttributesModel::setData(const QModelIndex &index, const QVariant &value, int role) {
+ QStandardItemModel::setData(index, value, role);
+
+ // TODO: attribute edit can fail for many reasons, handle it
+
+ // TODO: reload attributes in ad model
+
+ return true;
+}
diff --git a/src/attributes_model.h b/src/attributes_model.h
new file mode 100644
index 00000000..e07d44d4
--- /dev/null
+++ b/src/attributes_model.h
@@ -0,0 +1,21 @@
+#pragma once
+
+#include
+
+class AttributesModel: public QStandardItemModel {
+Q_OBJECT
+
+public:
+ enum Column {
+ Name,
+ Value,
+ COUNT,
+ };
+
+ explicit AttributesModel();
+
+ bool setData(const QModelIndex &index, const QVariant &value, int role = Qt::EditRole);
+
+private:
+
+};
diff --git a/src/attributes_view.c b/src/attributes_view.c
deleted file mode 100644
index e56c7450..00000000
--- a/src/attributes_view.c
+++ /dev/null
@@ -1,99 +0,0 @@
-
-#include "attributes_view.h"
-
-#include "constants.h"
-#include "utils.h"
-#include "entry.h"
-
-#include "stb_ds.h"
-#include
-
-// Lists attributes of target entry
-// Attributes target entry is selected in contents view
-
-enum {
- ATTRIBUTES_COLUMN_NAME,
- ATTRIBUTES_COLUMN_VALUE,
- ATTRIBUTES_COLUMN_COUNT
-};
-
-GtkTreeView* attributes_view = NULL;
-char* attributes_target = NULL;
-
-// NOTE: currently doesn't work for multi-valued attributes, add that capability when/if it's needed
-void attributes_value_edited_func(
- GtkCellRendererText* cell,
- gchar* path_string,
- gchar* new_text,
- gpointer user_data)
-{
- GtkTreeModel* model = gtk_tree_view_get_model(attributes_view);
-
- GtkTreeIter iter;
- gtk_tree_model_get_iter_from_string(model, &iter, path_string);
-
- char* attribute_name;
- gtk_tree_model_get(model, &iter, ATTRIBUTES_COLUMN_NAME, &attribute_name, -1);
-
- char* old_value;
- gtk_tree_model_get(model, &iter, ATTRIBUTES_COLUMN_VALUE, &old_value, -1);
-
- entry* e = get_entry(attributes_target);
- bool edit_success = entry_edit_value(e, attribute_name, new_text);
-
- if (edit_success) {
- gtk_tree_store_set(GTK_TREE_STORE(model), &iter, ATTRIBUTES_COLUMN_VALUE, new_text, -1);
-
- printf("attribute %s changed: %s > %s\n", attribute_name, old_value, new_text);
- } else {
- // TODO: display error
- }
-
- free(attribute_name);
- free(old_value);
-}
-
-void attributes_change_target(const char* new_target_dn) {
- if (attributes_target != NULL) {
- free(attributes_target);
- }
- attributes_target = strdup(new_target_dn);
-}
-
-// NOTE: model is set when an entry is selected in contents pane
-void attributes_populate_model() {
- // Populate model
- // List all key->value pairs in order
- GtkTreeStore* model = GTK_TREE_STORE(gtk_tree_view_get_model(attributes_view));
- gtk_tree_store_clear(model);
-
- entry* e = get_entry(attributes_target);
-
- // Target is invalid
- // NOTE: this is valid behavior and can occur when target entry is deleted for example
- if (e == NULL) {
- return;
- }
-
- STR_ARRAY attribute_keys = e->attribute_keys;
- for (int i = 0; i < arrlen(attribute_keys); i++) {
- char* key = attribute_keys[i];
- STR_ARRAY values = shget(e->attributes, key);
-
- for (int j = 0; j < arrlen(values); j++) {
- GtkTreeIter this_node;
- gtk_tree_store_append(model, &this_node, NULL);
- gtk_tree_store_set(model, &this_node, ATTRIBUTES_COLUMN_NAME, key, -1);
- gtk_tree_store_set(model, &this_node, ATTRIBUTES_COLUMN_VALUE, values[j], -1);
-
- // TODO: handle this, can't draw this text properly
- if (streql(key, "objectGUID")) {
- gtk_tree_store_set(model, &this_node, ATTRIBUTES_COLUMN_VALUE, "!!!can't render this for now", -1);
- }
- }
- }
-}
-
-void attributes_init(GtkBuilder* builder) {
- attributes_view = GTK_TREE_VIEW(gtk_builder_get_object_CHECKED(builder, "attributes_view"));
-}
diff --git a/src/attributes_view.cpp b/src/attributes_view.cpp
new file mode 100644
index 00000000..faf9e70d
--- /dev/null
+++ b/src/attributes_view.cpp
@@ -0,0 +1,42 @@
+
+#include "attributes_view.h"
+#include "ad_model.h"
+#include "ad_interface.h"
+
+// TODO: on attribute edit, update entry in ad model
+// since attributes affect model's contents
+
+void AttributesView::set_target_from_selection(const QItemSelection &selected, const QItemSelection &) {
+ // Clear model of previous root
+ // TODO: get rid of cast
+ auto model = qobject_cast(this->model());
+ if (model != nullptr) {
+ model->clear();
+ model->setHorizontalHeaderItem(Column::Name, new QStandardItem("Name"));
+ model->setHorizontalHeaderItem(Column::Value, new QStandardItem("Value"));
+ }
+
+ // Convert selection to dn
+ auto indexes = selected.indexes();
+ if (indexes.size() == 0) {
+ return;
+ }
+
+ auto index = indexes[0];
+ this->root_dn = index.data(AdModel::Roles::DN).toString();
+
+ // Populate model with attributes of new root
+ auto attributes = load_attributes(this->root_dn);
+ for (auto attribute : attributes.keys()) {
+ auto values = attributes[attribute];
+
+ for (auto value : values) {
+ auto name_item = new QStandardItem(attribute);
+ auto value_item = new QStandardItem(value);
+
+ name_item->setEditable(false);
+
+ model->appendRow({name_item, value_item});
+ }
+ }
+}
diff --git a/src/attributes_view.h b/src/attributes_view.h
index 626f84fc..783dde75 100644
--- a/src/attributes_view.h
+++ b/src/attributes_view.h
@@ -1,7 +1,25 @@
+
#pragma once
-#include
+#include
-void attributes_init(GtkBuilder* builder);
-void attributes_change_target(const char* new_target_dn);
-void attributes_populate_model();
+// Shows names and values of attributes of the entry selected in contents view
+class AttributesView : public QTreeView {
+Q_OBJECT
+
+public:
+ using QTreeView::QTreeView;
+
+public slots:
+ void set_target_from_selection(const QItemSelection &selected, const QItemSelection &deselected);
+
+private:
+ enum Column {
+ Name,
+ Value,
+ COUNT,
+ };
+
+ QString root_dn;
+
+};
diff --git a/src/constants.h b/src/constants.h
index 71b56cff..8f2d2fb4 100644
--- a/src/constants.h
+++ b/src/constants.h
@@ -1,4 +1,4 @@
+
#pragma once
-#define DN_LENGTH_MAX 1000
-#define HEAD_DN "DC=domain,DC=alt"
+#define HEAD_DN "DC=domain,DC=alt"
\ No newline at end of file
diff --git a/src/containers_view.c b/src/containers_view.c
deleted file mode 100644
index 623c6efc..00000000
--- a/src/containers_view.c
+++ /dev/null
@@ -1,143 +0,0 @@
-
-#include "containers_view.h"
-
-#include "contents_view.h"
-#include "constants.h"
-#include "utils.h"
-#include "entry.h"
-#include "menu_bar.h"
-
-#include "stb_ds.h"
-#include
-#include
-#include
-
-// Shows a tree of LDAP containers
-
-enum {
- CONTAINERS_COLUMN_DN,
- CONTAINERS_COLUMN_NAME,
-};
-
-GtkTreeView* containers_view = NULL;
-
-void containers_refilter() {
- GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(containers_view));
- gtk_tree_model_filter_refilter(model_filter);
-}
-
-void containers_row_expanded_func(
- GtkTreeView *tree_view,
- GtkTreeIter *iter,
- GtkTreePath *path,
- gpointer user_data)
-{
- GtkTreeModel* model = gtk_tree_view_get_model(tree_view);
-
- char* dn;
- gtk_tree_model_get(model, iter, CONTAINERS_COLUMN_DN, &dn, -1);
-
- printf("containers_row_expanded %s\n", dn);
-
- free(dn);
-}
-
-void containers_row_collapsed_func(
- GtkTreeView *tree_view,
- GtkTreeIter *iter,
- GtkTreePath *path,
- gpointer user_data)
-{
- // printf("containers_row_expanded %s\n", dn);
-}
-
-void containers_selection_changed_func(GtkTreeSelection* selection, gpointer user_data) {
- // Get selected iter
- GtkTreeView* view = gtk_tree_selection_get_tree_view(selection);
- GtkTreeModel* model = gtk_tree_view_get_model(view);
- GtkTreeIter iter;
- gboolean any_selected = gtk_tree_selection_get_selected(selection, &model, &iter);
- if (!any_selected) {
- return;
- }
-
- // Get dn of selected iter
- char* dn;
- gtk_tree_model_get(model, &iter, CONTAINERS_COLUMN_DN, &dn, -1);
-
- // Update contents model
- contents_change_target(dn);
- contents_populate_model();
-
- free(dn);
-}
-
-gboolean containers_filter_func(
- GtkTreeModel *model,
- GtkTreeIter *iter,
- gpointer data)
-{
- // Filter out entries with "showInAdvancedViewOnly" set to TRUE
- gboolean visible = TRUE;
-
- char* dn;
- gtk_tree_model_get(model, iter, CONTAINERS_COLUMN_DN, &dn, -1);
-
- if (dn != NULL) {
- entry* e = get_entry(dn);
- char* showInAdvancedViewOnly = entry_get_attribute_or_none(e, "showInAdvancedViewOnly");
- if (!advanced_view_is_on() && streql(showInAdvancedViewOnly, "TRUE")) {
- visible = FALSE;
- }
-
- g_free(dn);
- }
-
- return visible;
-}
-
-void containers_populate_model_recursive(GtkTreeStore* model, char* node_dn, GtkTreeIter* parent) {
- // Populate model with name's of entries
- entry* e = get_entry(node_dn);
-
- // Skip if entry is not a container
- if (!entry_is_container(e)) {
- return;
- }
-
- GtkTreeIter this_node;
- gtk_tree_store_append(model, &this_node, parent);
-
- gtk_tree_store_set(model, &this_node, CONTAINERS_COLUMN_DN, e->dn, -1);
-
- char name[DN_LENGTH_MAX];
- first_element_in_dn(name, e->dn, DN_LENGTH_MAX);
- gtk_tree_store_set(model, &this_node, CONTAINERS_COLUMN_NAME, name, -1);
-
- // Recurse into entry's children
- for (int i = 0; i < arrlen(e->children); i++) {
- char* child_dn = e->children[i];
-
- containers_populate_model_recursive(model, child_dn, &this_node);
- }
-}
-
-void containers_populate_model() {
- GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(containers_view));
- GtkTreeStore* model = GTK_TREE_STORE(gtk_tree_model_filter_get_model(model_filter));
- gtk_tree_store_clear(model);
-
- containers_populate_model_recursive(model, HEAD_DN, NULL);
-
- gtk_tree_model_filter_refilter(model_filter);
-}
-
-void containers_init(GtkBuilder* builder) {
- containers_view = GTK_TREE_VIEW(gtk_builder_get_object_CHECKED(builder, "containers_view"));
-
- // Set filter func
- GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER((gtk_tree_view_get_model(containers_view)));
- gtk_tree_model_filter_set_visible_func(model_filter, containers_filter_func, NULL, NULL);
-
- containers_populate_model();
-}
diff --git a/src/containers_view.cpp b/src/containers_view.cpp
new file mode 100644
index 00000000..6a8ac348
--- /dev/null
+++ b/src/containers_view.cpp
@@ -0,0 +1,10 @@
+
+#include "containers_view.h"
+#include "ad_model.h"
+
+#include
+#include
+
+ContainersView::ContainersView(QWidget *parent) : QTreeView(parent) {
+
+};
diff --git a/src/containers_view.h b/src/containers_view.h
index 305ca596..723ed0c6 100644
--- a/src/containers_view.h
+++ b/src/containers_view.h
@@ -1,7 +1,19 @@
+
#pragma once
-#include
+#include
-void containers_init(GtkBuilder* builder);
-void containers_refilter();
-void containers_populate_model();
+// Shows names of AdModel as a tree
+class ContainersView : public QTreeView {
+Q_OBJECT
+
+public:
+ explicit ContainersView(QWidget *parent = nullptr);
+
+public slots:
+
+signals:
+
+private:
+
+};
diff --git a/src/contents_view.c b/src/contents_view.c
deleted file mode 100644
index a88eedf7..00000000
--- a/src/contents_view.c
+++ /dev/null
@@ -1,135 +0,0 @@
-
-#include "contents_view.h"
-
-#include "attributes_view.h"
-#include "entry.h"
-#include "constants.h"
-#include "menu_bar.h"
-#include "utils.h"
-
-#include "stb_ds.h"
-#include
-
-// Shows a list of LDAP entries that children of current target entry
-// Contents target entry is selected in containers view
-
-enum {
- CONTENTS_COLUMN_DN,
- CONTENTS_COLUMN_NAME,
- CONTENTS_COLUMN_CATEGORY,
- CONTENTS_COLUMN_DESCRIPTION,
- CONTENTS_COLUMN_COUNT
-};
-
-GtkTreeView* contents_view = NULL;
-char* contents_target = NULL;
-
-void contents_refilter() {
- GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(contents_view));
- gtk_tree_model_filter_refilter(model_filter);
-}
-
-gboolean contents_filter_func(
- GtkTreeModel *model,
- GtkTreeIter *iter,
- gpointer data)
-{
- // Filter out entries with "showInAdvancedViewOnly" set to TRUE
- gboolean visible = TRUE;
-
- char* dn;
- gtk_tree_model_get(model, iter, CONTENTS_COLUMN_DN, &dn, -1);
- if (dn != NULL) {
- entry* e = get_entry(dn);
- STR_ARRAY showInAdvancedViewOnly = entry_get_attribute(e, "showInAdvancedViewOnly");
- if (!advanced_view_is_on() && showInAdvancedViewOnly != NULL && streql(showInAdvancedViewOnly[0], "TRUE")) {
- visible = FALSE;
- }
-
- g_free(dn);
- }
-
- return visible;
-}
-
-void contents_selection_changed_func(GtkTreeSelection* selection, gpointer user_data) {
- GtkTreeView* view = gtk_tree_selection_get_tree_view(selection);
- GtkTreeModel* model = gtk_tree_view_get_model(view);
-
- GtkTreeIter iter;
-
- gboolean any_selected = gtk_tree_selection_get_selected(selection, &model, &iter);
- if (!any_selected) {
- return;
- }
-
- // Get dn of selected iter
- char* dn;
- gtk_tree_model_get(model, &iter, CONTENTS_COLUMN_DN, &dn, -1);
-
- // Update contents model
- attributes_change_target(dn);
- attributes_populate_model();
-
- free(dn);
-}
-
-void contents_change_target(const char* new_target_dn) {
- if (contents_target != NULL) {
- free(contents_target);
- }
- contents_target = strdup(new_target_dn);
-}
-
-// NOTE: contents model is repopulated everytime a new container is selected
-void contents_populate_model() {
- GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER(gtk_tree_view_get_model(contents_view));
- GtkListStore* model = GTK_LIST_STORE(gtk_tree_model_filter_get_model(model_filter));
- gtk_list_store_clear(model);
-
- if (contents_target == NULL) {
- printf("ERROR contents_populate_model(): no target\n");
- return;
- }
-
- entry* e = get_entry(contents_target);
-
- // Target is invalid
- // NOTE: this is valid behavior and can occur when target entry is deleted for example
- if (e == NULL) {
- return;
- }
-
- // Populate model
- for (int i = 0; i < arrlen(e->children); i++) {
- char* child_dn = e->children[i];
- entry* child = get_entry(child_dn);
-
- GtkTreeIter this_node;
- gtk_list_store_append(model, &this_node);
-
- char* dn = child->dn;
- gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_DN, dn, -1);
-
- char name[DN_LENGTH_MAX];
- first_element_in_dn(name, dn, DN_LENGTH_MAX);
- gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_NAME, name, -1);
-
- char* category_dn = entry_get_attribute_or_none(child, "objectCategory");
- gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_CATEGORY, category_dn, -1);
-
- char* description = entry_get_attribute_or_none(child, "description");
- gtk_list_store_set(model, &this_node, CONTENTS_COLUMN_DESCRIPTION, description, -1);
- }
-
- gtk_tree_model_filter_refilter(model_filter);
-}
-
-void contents_init(GtkBuilder* builder) {
- contents_view = GTK_TREE_VIEW(gtk_builder_get_object_CHECKED(builder, "contents_view"));
-
- // Set filter func
- GtkTreeModelFilter* model_filter = GTK_TREE_MODEL_FILTER((gtk_tree_view_get_model(contents_view)));
- gtk_tree_model_filter_set_visible_func(model_filter, contents_filter_func, NULL, NULL);
- gtk_tree_model_filter_refilter(model_filter);
-}
diff --git a/src/contents_view.cpp b/src/contents_view.cpp
new file mode 100644
index 00000000..6db45ebb
--- /dev/null
+++ b/src/contents_view.cpp
@@ -0,0 +1,40 @@
+
+#include "contents_view.h"
+
+#include
+#include
+
+void ContentsView::set_root_index_from_selection(const QItemSelection &selected, const QItemSelection &) {
+ auto indexes = selected.indexes();
+
+ if (indexes.size() == 0) {
+ return;
+ }
+
+ // Map from proxy model of given index to source model of this view (if needed)
+ auto source_index = indexes[0];
+ {
+ auto model = source_index.model();
+ auto proxy_model = qobject_cast(model);
+ if (proxy_model != nullptr) {
+ source_index = proxy_model->mapToSource(source_index);
+ }
+ }
+
+ // Map from source model of this view to proxy model of this view (if needed)
+ auto contents_index = source_index;
+ {
+ auto model = this->model();
+ auto proxy_model = qobject_cast(model);
+ if (proxy_model != nullptr) {
+ contents_index = proxy_model->mapFromSource(contents_index);
+ }
+ }
+
+ if (!this->model()->checkIndex(contents_index)) {
+ printf("ContentsView::set_root_index_from_selection received bad index!\n");
+ return;
+ }
+
+ this->setRootIndex(contents_index);
+}
diff --git a/src/contents_view.h b/src/contents_view.h
index 45ce6e86..c03c2e81 100644
--- a/src/contents_view.h
+++ b/src/contents_view.h
@@ -1,9 +1,18 @@
+
#pragma once
-#include
+#include
-void contents_init(GtkBuilder* builder);
-void contents_change_target(const char* new_target_dn);
-void contents_populate_model();
-void contents_refilter();
-void contents_selection_changed_func(GtkTreeSelection* selection, gpointer user_data);
\ No newline at end of file
+// Shows name, category and description of children of entry selected in containers view
+class ContentsView : public QTreeView {
+Q_OBJECT
+
+public:
+ using QTreeView::QTreeView;
+
+public slots:
+ void set_root_index_from_selection(const QItemSelection &selected, const QItemSelection &);
+
+private:
+
+};
\ No newline at end of file
diff --git a/src/entry.c b/src/entry.c
deleted file mode 100644
index b7b9ac8a..00000000
--- a/src/entry.c
+++ /dev/null
@@ -1,409 +0,0 @@
-
-#include "entry.h"
-
-#include "containers_view.h"
-#include "contents_view.h"
-#include "utils.h"
-#include "constants.h"
-
-// NOTE: need to define this once in one .c file
-#define STB_DS_IMPLEMENTATION
-#include "stb_ds.h"
-#include "active_directory.h"
-#include
-#include
-
-entries_map* entries;
-
-entry* get_entry(const char* dn) {
- return shget(entries, dn);
-}
-
-void entry_load(const char* dn) {
- entry* e = (entry*)malloc(sizeof(entry));
-
- e->dn = strdup(dn);
-
- //
- // Load entry's attributes
- //
- e->attributes = NULL;
- e->attribute_keys = NULL;
- char** attributes_raw = ad_get_attribute(e->dn, NULL);
- if (attributes_raw != NULL) {
- // Init entry's attributes map
- sh_new_strdup(e->attributes);
-
- // Load e->attributes
- // attributes_raw is in the form of:
- // char** array of [key, value, value, key, value ...]
- // it's a map of keys to values where keys can map to multiple values
- // transform it into:
- // stb map of [key => [value, value ...], key => [value, value ...] ...]
- for (int i = 0; attributes_raw[i + 2] != NULL; i += 2) {
- char* key = attributes_raw[i];
-
- // Get current values of this attribute
- STR_ARRAY values = shget(e->attributes, key);
- // Append new value to values list
- arrput(values, strdup(attributes_raw[i + 1]));
- // Update values in attributes map
- shput(e->attributes, key, values);
- }
-
- // Load e->attributes_keys, which contains keys in their original ordering
- char* current_key = NULL;
- for (int i = 0; attributes_raw[i + 2] != NULL; i += 2) {
- char* key = attributes_raw[i];
-
- if (current_key == NULL || !streql(current_key, key)) {
- // Encountered a new key, save it
- arrput(e->attribute_keys, strdup(key));
- current_key = key;
- }
- }
-
- // Free attributes_raw
- for (int i = 0; attributes_raw[i] != NULL; i++) {
- free(attributes_raw[i]);
- }
- free(attributes_raw);
- }
-
- //
- // Load children list
- //
- e->children = NULL;
- char** children_dns = ad_list(e->dn);
- if (children_dns != NULL) {
- for (int i = 0; children_dns[i] != NULL; i++) {
- char* child_dn = children_dns[i];
- arrput(e->children, strdup(child_dn));
-
- // Preload children
- entry_load(child_dn);
- }
-
- free(children_dns);
- }
-
- // Add entry to entries map
- shput(entries, e->dn, e);
-}
-
-void entry_init() {
- // Init map to use strdup for string allocation
- sh_new_strdup(entries);
-
- // Load entries recursively
- entry_load(HEAD_DN);
-}
-
-STR_ARRAY entry_get_attribute(entry* e, const char* key) {
- return shget(e->attributes, key);
-}
-
-char* entry_get_attribute_or_none(entry* e, const char* key) {
- static char* none_str = "none";
- STR_ARRAY attribute_array = entry_get_attribute(e, key);
- if (attribute_array != NULL) {
- return attribute_array[0];
- } else {
- return none_str;
- }
-}
-
-bool entry_attribute_exists(entry* e, const char* key, const char* value) {
- STR_ARRAY values = shget(e->attributes, key);
-
- if (values == NULL) {
- return false;
- } else {
- for (int i = 0; i < arrlen(values); i++) {
- if (streql(values[i], value)) {
- return true;
- }
- }
-
- return false;
- }
-}
-
-void entry_delete(entry* e) {
- int result = ad_object_delete(e->dn);
-
- if (result == AD_SUCCESS) {
- // Delete entry from parent's child list
- // Get parent dn by cutting part before first comma
- // TODO: get this dn in a better way
- char* dn = e->dn;
- const char* comma = strchr(dn, ',');
- char parent_dn[DN_LENGTH_MAX];
- strncpy(parent_dn, comma, DN_LENGTH_MAX);
-
- entry* parent = shget(entries, parent_dn);
- if (parent != NULL) {
- // Find child index in children array
- STR_ARRAY children = parent->children;
- int delete_i = -1;
- for (int i = 0; i < arrlen(children); i++) {
- char* child = children[i];
- if (streql(child, dn)) {
- delete_i = i;
- break;
- }
- }
-
- if (delete_i != -1) {
- arrdel(children, delete_i);
- }
- }
-
- //
- // Free entry and all it's members
- //
- shdel(entries, e->dn);
-
- free(e->dn);
-
- // Free attributes
- for (int i = 0; i < shlen(e->attributes); i++) {
- STR_ARRAY values = e->attributes[i].value;
-
- for (int j = 0; j < arrlen(values); j++) {
- char* attribute = values[j];
- free(attribute);
- }
-
- arrfree(values);
- }
- shfree(e->attributes);
-
- // Free attribute_keys
- for (int i = 0; i < arrlen(e->attribute_keys); i++) {
- char* key = e->attribute_keys[i];
- free(key);
- }
- arrfree(e->attribute_keys);
-
- // Free children
- for (int i = 0; i < arrlen(e->children); i++) {
- free(e->children[i]);
- }
- arrfree(e->children);
-
- // Replicate changes in view models
- containers_populate_model();
- contents_populate_model();
- } else {
- printf("ad_object_delete error: %s\n", ad_get_error());
- }
-}
-
-// TODO: probably need to add more objectClasses
-bool entry_is_container(entry* e) {
- static const char* container_objectClasses[4] = {"container", "organizationalUnit", "builtinDomain", "domain"};
- int container_objectClasses_size = sizeof(container_objectClasses) / sizeof(container_objectClasses[0]);
-
- for (int i = 0; i < container_objectClasses_size; i++) {
- if (entry_attribute_exists(e, "objectClass", container_objectClasses[i])) {
- return true;
- }
- }
-
- return false;
-}
-
-// "OU=Something,CN=Blah,CN=Bleh" => "Something"
-void first_element_in_dn(char* buffer, const char* dn, size_t buffer_size) {
- if (buffer == NULL) {
- return;
- }
-
- *buffer = '\0';
-
- if (dn == NULL) {
- return;
- }
-
- // Remove part before first "="
- const char* equals_ptr = strchr(dn, '=');
- if (equals_ptr == NULL || strlen(equals_ptr) <= 1) {
- return;
- }
- strncpy(buffer, equals_ptr + 1, buffer_size);
-
- // Remove part after first ","
- char* comma_ptr = strchr(buffer, ',');
- if (comma_ptr == NULL) {
- return;
- }
- *comma_ptr = '\0';
-}
-
-// NOTE: doesn't handle multi-valued for now
-bool entry_edit_value(entry* e, char* key, char* new_value) {
- int result = ad_mod_replace(e->dn, key, new_value);
-
- STR_ARRAY values = entry_get_attribute(e, key);
-
- if (result == AD_SUCCESS) {
- free(values[0]);
- values[0] = strdup(new_value);
-
- return true;
- } else {
- printf("ad_mod_replace error: %s\n", ad_get_error());
-
- return false;
- }
-}
-
-void add_fake_attribute(entry* e, const char* key, const char* value) {
- STR_ARRAY values = NULL;
- arrput(values, strdup(value));
- shput(e->attributes, key, values);
-}
-
-entry* make_fake_entry(const char* name, entry* parent, bool container, const char* category) {
- entry* e = (entry*)malloc(sizeof(entry));
-
- char dn[1000];
- if (parent == NULL) {
- strcpy(dn, HEAD_DN);
- } else {
- strcpy(dn, "CN=");
- strcat(dn, name);
- strcat(dn, ",");
- strcat(dn, parent->dn);
- }
-
- e->dn = strdup(dn);
- e->attributes = NULL;
- sh_new_strdup(e->attributes);
- e->attribute_keys = NULL;
- e->children = NULL;
- shput(entries, dn, e);
-
- if (parent != NULL) {
- arrput(parent->children, strdup(e->dn));
- }
-
- add_fake_attribute(e, "name", name);
-
- if (container) {
- add_fake_attribute(e, "objectClass", "container");
- } else {
- add_fake_attribute(e, "objectClass", "class");
- }
-
- add_fake_attribute(e, "objectCategory", category);
-
- for (int i = 0; i < shlen(e->attributes); i++) {
- attributes_map a = e->attributes[i];
- arrput(e->attribute_keys, strdup(a.key));
- }
-
- return e;
-}
-
-void entry_init_fake() {
- // Init map to use strdup for string allocation
- sh_new_strdup(entries);
-
- // Load entries recursively
- entry_load(HEAD_DN);
-
- // HEAD_DN= "DC=domain,DC=alt"
- entry* head = make_fake_entry(HEAD_DN, NULL, true, "Person");
-
- entry* dave = make_fake_entry("dave", head, true, "Person");
- make_fake_entry("daves_dog", dave, false, "Robot");
- make_fake_entry("daves_car", dave, false, "Robot");
- add_fake_attribute(dave, "description", "dave is a cool dude");
-
- entry* mark = make_fake_entry("mark", head, true, "Person");
- make_fake_entry("marks_son", mark, false, "Robot");
- make_fake_entry("marks_daughter", mark, false, "Robot");
-
- entry* alice = make_fake_entry("alice", head, true, "Person");
- make_fake_entry("alices_child", alice, false, "Robot");
- make_fake_entry("alices_son", alice, false, "Person");
- make_fake_entry("alices_second_son", alice, false, "Person");
-
- entry* advanced_bob = make_fake_entry("advanced_bob", head, true, "Person");
- make_fake_entry("advanced_bobs_son", advanced_bob, false, "Robot");
- make_fake_entry("advanced_bobs_daughter", advanced_bob, false, "Robot");
- add_fake_attribute(advanced_bob, "showInAdvancedViewOnly", "TRUE");
-
- entry* advanced_klaren = make_fake_entry("advanced_klaren", head, true, "Person");
- make_fake_entry("advanced_klarens_son", advanced_klaren, false, "Robot");
- make_fake_entry("advanced_klarens_daughter", advanced_klaren, false, "Robot");
- add_fake_attribute(advanced_klaren, "showInAdvancedViewOnly", "TRUE");
-}
-
-bool entry_new(const char* name, NewEntryType type) {
- return;
-
- // TODO: compose parent DN
- // Users will be "HEAD_DN" + Users
- // Computers will be "HEAD_DN" + Computers
- char parent_dn[DN_LENGTH_MAX];
- switch (type) {
- case NewEntryType_User: {
- snrpintf("CN=Users,%s", DN_LENGTH_MAX, HEAD_DN);
- break;
- }
- case NewEntryType_Computer: {
- snrpintf("CN=Computers,%s", DN_LENGTH_MAX, HEAD_DN);
- break;
- }
- case NewEntryType_OU: {
- snrpintf("CN=Computers,%s", DN_LENGTH_MAX, HEAD_DN);
- break;
- }
- case NewEntryType_Group: {
- break;
- }
- }
-
- entry* parent = shget(entries, parent_dn);
-
- char entry_dn[DN_LENGTH_MAX];
- snrpintf("CN=%s,%s", DN_LENGTH_MAX, name, parent->dn);
-
- int result;
- switch (type) {
- case NewEntryType_User: {
- result = ad_create_user(name, entry_dn);
- break;
- }
- case NewEntryType_Computer: {
- result = ad_create_computer(name, entry_dn);
- break;
- }
- case NewEntryType_OU: {
- result = ad_ou_create(name, entry_dn);
- break;
- }
- case NewEntryType_Group: {
- result = ad_group_create(name, entry_dn);
- break;
- }
- }
-
- if (result == AD_SUCCESS) {
- entry_load(entry_dn);
-
- // Add new netry to parent's children
- STR_ARRAY parent_children = parent->children;
- arrput(parent_children, strdup(entry_dn));
-
- // Replicate changes in view models
- containers_populate_model();
- contents_populate_model();
- } else {
- // TODO: display error in GUI
- printf("new entry error: %s\n", ad_get_error());
- }
-}
diff --git a/src/entry.h b/src/entry.h
deleted file mode 100644
index ab50c3af..00000000
--- a/src/entry.h
+++ /dev/null
@@ -1,48 +0,0 @@
-
-#ifndef ENTRY_H
-#define ENTRY_H
-
-#include
-#include
-
-// for use with stb array functions
-typedef char** STR_ARRAY;
-
-typedef struct attributes_map {
- char* key;
- STR_ARRAY value;
-} attributes_map;
-
-typedef struct entry {
- char* dn;
- attributes_map* attributes;
- STR_ARRAY attribute_keys;
- STR_ARRAY children;
-} entry;
-
-typedef struct entries_map {
- char* key;
- entry* value;
-} entries_map;
-
-typedef enum {
- NewEntryType_User,
- NewEntryType_Computer,
- NewEntryType_OU,
- NewEntryType_Group
-} NewEntryType;
-
-void entry_init();
-void entry_init_fake();
-void entry_load(const char* dn);
-STR_ARRAY entry_get_attribute(entry* e, const char* key);
-char* entry_get_attribute_or_none(entry* e, const char* key);
-bool entry_new(const char* name, NewEntryType type);
-entry* get_entry(const char* dn);
-void entry_delete(entry* e);
-bool entry_attribute_exists(entry* e, const char* key, const char* value);
-bool entry_is_container(entry* e);
-void first_element_in_dn(char* buffer, const char* dn, size_t buffer_size);
-bool entry_edit_value(entry* e, char* key, char* new_value);
-
-#endif
diff --git a/src/libs/stb_ds.h b/src/libs/stb_ds.h
deleted file mode 100644
index 4c1b2210..00000000
--- a/src/libs/stb_ds.h
+++ /dev/null
@@ -1,1828 +0,0 @@
-/* stb_ds.h - v0.64 - public domain data structures - Sean Barrett 2019
-
- This is a single-header-file library that provides easy-to-use
- dynamic arrays and hash tables for C (also works in C++).
-
- For a gentle introduction:
- http://nothings.org/stb_ds
-
- To use this library, do this in *one* C or C++ file:
- #define STB_DS_IMPLEMENTATION
- #include "stb_ds.h"
-
-TABLE OF CONTENTS
-
- Table of Contents
- Compile-time options
- License
- Documentation
- Notes
- Notes - Dynamic arrays
- Notes - Hash maps
- Credits
-
-COMPILE-TIME OPTIONS
-
- #define STBDS_NO_SHORT_NAMES
-
- This flag needs to be set globally.
-
- By default stb_ds exposes shorter function names that are not qualified
- with the "stbds_" prefix. If these names conflict with the names in your
- code, define this flag.
-
- #define STBDS_SIPHASH_2_4
-
- This flag only needs to be set in the file containing #define STB_DS_IMPLEMENTATION.
-
- By default stb_ds.h hashes using a weaker variant of SipHash and a custom hash for
- 4- and 8-byte keys. On 64-bit platforms, you can define the above flag to force
- stb_ds.h to use specification-compliant SipHash-2-4 for all keys. Doing so makes
- hash table insertion about 20% slower on 4- and 8-byte keys, 5% slower on
- 64-byte keys, and 10% slower on 256-byte keys on my test computer.
-
- #define STBDS_REALLOC(context,ptr,size) better_realloc
- #define STBDS_FREE(context,ptr) better_free
-
- These defines only need to be set in the file containing #define STB_DS_IMPLEMENTATION.
-
- By default stb_ds uses stdlib realloc() and free() for memory management. You can
- substitute your own functions instead by defining these symbols. You must either
- define both, or neither. Note that at the moment, 'context' will always be NULL.
- @TODO add an array/hash initialization function that takes a memory context pointer.
-
- #define STBDS_UNIT_TESTS
-
- Defines a function stbds_unit_tests() that checks the functioning of the data structures.
-
- Note that on older versions of gcc (e.g. 5.x.x) you may need to build with '-std=c++0x'
- (or equivalentally '-std=c++11') when using anonymous structures as seen on the web
- page or in STBDS_UNIT_TESTS.
-
-LICENSE
-
- Placed in the public domain and also MIT licensed.
- See end of file for detailed license information.
-
-DOCUMENTATION
-
- Dynamic Arrays
-
- Non-function interface:
-
- Declare an empty dynamic array of type T
- T* foo = NULL;
-
- Access the i'th item of a dynamic array 'foo' of type T, T* foo:
- foo[i]
-
- Functions (actually macros)
-
- arrfree:
- void arrfree(T*);
- Frees the array.
-
- arrlen:
- ptrdiff_t arrlen(T*);
- Returns the number of elements in the array.
-
- arrlenu:
- size_t arrlenu(T*);
- Returns the number of elements in the array as an unsigned type.
-
- arrpop:
- T arrpop(T* a)
- Removes the final element of the array and returns it.
-
- arrput:
- T arrput(T* a, T b);
- Appends the item b to the end of array a. Returns b.
-
- arrins:
- T arrins(T* a, int p, T b);
- Inserts the item b into the middle of array a, into a[p],
- moving the rest of the array over. Returns b.
-
- arrinsn:
- void arrins(T* a, int p, int n);
- Inserts n uninitialized items into array a starting at a[p],
- moving the rest of the array over.
-
- arraddn:
- T* arraddn(T* a, int n)
- Appends n uninitialized items onto array at the end.
- Returns a pointer to the first uninitialized item added.
-
- arrdel:
- void arrdel(T* a, int p);
- Deletes the element at a[p], moving the rest of the array over.
-
- arrdeln:
- void arrdel(T* a, int p, int n);
- Deletes n elements starting at a[p], moving the rest of the array over.
-
- arrdelswap:
- void arrdelswap(T* a, int p);
- Deletes the element at a[p], replacing it with the element from
- the end of the array. O(1) performance.
-
- arrsetlen:
- void arrsetlen(T* a, int n);
- Changes the length of the array to n. Allocates uninitialized
- slots at the end if necessary.
-
- arrsetcap:
- size_t arrsetcap(T* a, int n);
- Sets the length of allocated storage to at least n. It will not
- change the length of the array.
-
- arrcap:
- size_t arrcap(T* a);
- Returns the number of total elements the array can contain without
- needing to be reallocated.
-
- Hash maps & String hash maps
-
- Given T is a structure type: struct { TK key; TV value; }. Note that some
- functions do not require TV value and can have other fields. For string
- hash maps, TK must be 'char *'.
-
- Special interface:
-
- stbds_rand_seed:
- void stbds_rand_seed(size_t seed);
- For security against adversarially chosen data, you should seed the
- library with a strong random number. Or at least seed it with time().
-
- stbds_hash_string:
- size_t stbds_hash_string(char *str, size_t seed);
- Returns a hash value for a string.
-
- stbds_hash_bytes:
- size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
- These functions hash an arbitrary number of bytes. The function
- uses a custom hash for 4- and 8-byte data, and a weakened version
- of SipHash for everything else. On 64-bit platforms you can get
- specification-compliant SipHash-2-4 on all data by defining
- STBDS_SIPHASH_2_4, at a significant cost in speed.
-
- Non-function interface:
-
- Declare an empty hash map of type T
- T* foo = NULL;
-
- Access the i'th entry in a hash table T* foo:
- foo[i]
-
- Function interface (actually macros):
-
- hmfree
- shfree
- void hmfree(T*);
- void shfree(T*);
- Frees the hashmap and sets the pointer to NULL.
-
- hmlen
- shlen
- ptrdiff_t hmlen(T*)
- ptrdiff_t shlen(T*)
- Returns the number of elements in the hashmap.
-
- hmlenu
- shlenu
- size_t hmlenu(T*)
- size_t shlenu(T*)
- Returns the number of elements in the hashmap.
-
- hmgeti
- shgeti
- hmgeti_ts
- ptrdiff_t hmgeti(T*, TK key)
- ptrdiff_t shgeti(T*, char* key)
- ptrdiff_t hmgeti_ts(T*, TK key, ptrdiff_t tempvar)
- Returns the index in the hashmap which has the key 'key', or -1
- if the key is not present.
-
- hmget
- hmget_ts
- shget
- TV hmget(T*, TK key)
- TV shget(T*, char* key)
- TV hmget_ts(T*, TK key, ptrdiff_t tempvar)
- Returns the value corresponding to 'key' in the hashmap.
- The structure must have a 'value' field
-
- hmgets
- shgets
- T hmgets(T*, TK key)
- T shgets(T*, char* key)
- Returns the structure corresponding to 'key' in the hashmap.
-
- hmgetp
- shgetp
- hmgetp_ts
- hmgetp_null
- shgetp_null
- T* hmgetp(T*, TK key)
- T* shgetp(T*, char* key)
- T* hmgetp_ts(T*, TK key, ptrdiff_t tempvar)
- T* hmgetp_null(T*, TK key)
- T* shgetp_null(T*, char *key)
- Returns a pointer to the structure corresponding to 'key' in
- the hashmap. Functions ending in "_null" return NULL if the key
- is not present in the hashmap; the others return a pointer to a
- structure holding the default value (but not the searched-for key).
-
- hmdefault
- shdefault
- TV hmdefault(T*, TV value)
- TV shdefault(T*, TV value)
- Sets the default value for the hashmap, the value which will be
- returned by hmget/shget if the key is not present.
-
- hmdefaults
- shdefaults
- TV hmdefaults(T*, T item)
- TV shdefaults(T*, T item)
- Sets the default struct for the hashmap, the contents which will be
- returned by hmgets/shgets if the key is not present.
-
- hmput
- shput
- TV hmput(T*, TK key, TV value)
- TV shput(T*, char* key, TV value)
- Inserts a pair into the hashmap. If the key is already
- present in the hashmap, updates its value.
-
- hmputs
- shputs
- T hmputs(T*, T item)
- T shputs(T*, T item)
- Inserts a struct with T.key into the hashmap. If the struct is already
- present in the hashmap, updates it.
-
- hmdel
- shdel
- int hmdel(T*, TK key)
- int shdel(T*, char* key)
- If 'key' is in the hashmap, deletes its entry and returns 1.
- Otherwise returns 0.
-
- Function interface (actually macros) for strings only:
-
- sh_new_strdup
- void sh_new_strdup(T*);
- Overwrites the existing pointer with a newly allocated
- string hashmap which will automatically allocate and free
- each string key using realloc/free
-
- sh_new_arena
- void sh_new_arena(T*);
- Overwrites the existing pointer with a newly allocated
- string hashmap which will automatically allocate each string
- key to a string arena. Every string key ever used by this
- hash table remains in the arena until the arena is freed.
- Additionally, any key which is deleted and reinserted will
- be allocated multiple times in the string arena.
-
-NOTES
-
- * These data structures are realloc'd when they grow, and the macro
- "functions" write to the provided pointer. This means: (a) the pointer
- must be an lvalue, and (b) the pointer to the data structure is not
- stable, and you must maintain it the same as you would a realloc'd
- pointer. For example, if you pass a pointer to a dynamic array to a
- function which updates it, the function must return back the new
- pointer to the caller. This is the price of trying to do this in C.
-
- * The following are the only functions that are thread-safe on a single data
- structure, i.e. can be run in multiple threads simultaneously on the same
- data structure
- hmlen shlen
- hmlenu shlenu
- hmget_ts shget_ts
- hmgeti_ts shgeti_ts
- hmgets_ts shgets_ts
-
- * You iterate over the contents of a dynamic array and a hashmap in exactly
- the same way, using arrlen/hmlen/shlen:
-
- for (i=0; i < arrlen(foo); ++i)
- ... foo[i] ...
-
- * All operations except arrins/arrdel are O(1) amortized, but individual
- operations can be slow, so these data structures may not be suitable
- for real time use. Dynamic arrays double in capacity as needed, so
- elements are copied an average of once. Hash tables double/halve
- their size as needed, with appropriate hysteresis to maintain O(1)
- performance.
-
-NOTES - DYNAMIC ARRAY
-
- * If you know how long a dynamic array is going to be in advance, you can avoid
- extra memory allocations by using arrsetlen to allocate it to that length in
- advance and use foo[n] while filling it out, or arrsetcap to allocate the memory
- for that length and use arrput/arrpush as normal.
-
- * Unlike some other versions of the dynamic array, this version should
- be safe to use with strict-aliasing optimizations.
-
-NOTES - HASH MAP
-
- * For compilers other than GCC and clang (e.g. Visual Studio), for hmput/hmget/hmdel
- and variants, the key must be an lvalue (so the macro can take the address of it).
- Extensions are used that eliminate this requirement if you're using C99 and later
- in GCC or clang, or if you're using C++ in GCC. But note that this can make your
- code less portable.
-
- * To test for presence of a key in a hashmap, just do 'hmgeti(foo,key) >= 0'.
-
- * The iteration order of your data in the hashmap is determined solely by the
- order of insertions and deletions. In particular, if you never delete, new
- keys are always added at the end of the array. This will be consistent
- across all platforms and versions of the library. However, you should not
- attempt to serialize the internal hash table, as the hash is not consistent
- between different platforms, and may change with future versions of the library.
-
- * Use sh_new_arena() for string hashmaps that you never delete from. Initialize
- with NULL if you're managing the memory for your strings, or your strings are
- never freed (at least until the hashmap is freed). Otherwise, use sh_new_strdup().
- @TODO: make an arena variant that garbage collects the strings with a trivial
- copy collector into a new arena whenever the table shrinks / rebuilds. Since
- current arena recommendation is to only use arena if it never deletes, then
- this can just replace current arena implementation.
-
- * If adversarial input is a serious concern and you're on a 64-bit platform,
- enable STBDS_SIPHASH_2_4 (see the 'Compile-time options' section), and pass
- a strong random number to stbds_rand_seed.
-
- * The default value for the hash table is stored in foo[-1], so if you
- use code like 'hmget(T,k)->value = 5' you can accidentally overwrite
- the value stored by hmdefault if 'k' is not present.
-
-CREDITS
-
- Sean Barrett -- library, idea for dynamic array API/implementation
- Per Vognsen -- idea for hash table API/implementation
- Rafael Sachetto -- arrpop()
-
- Bugfixes:
- Andy Durdin
- Shane Liesegang
- Vinh Truong
-*/
-
-#ifdef STBDS_UNIT_TESTS
-#define _CRT_SECURE_NO_WARNINGS
-#endif
-
-#ifndef INCLUDE_STB_DS_H
-#define INCLUDE_STB_DS_H
-
-#include
-#include
-
-#ifndef STBDS_NO_SHORT_NAMES
-#define arrlen stbds_arrlen
-#define arrlenu stbds_arrlenu
-#define arrput stbds_arrput
-#define arrpush stbds_arrput
-#define arrpop stbds_arrpop
-#define arrfree stbds_arrfree
-#define arraddn stbds_arraddn
-#define arrsetlen stbds_arrsetlen
-#define arrlast stbds_arrlast
-#define arrins stbds_arrins
-#define arrinsn stbds_arrinsn
-#define arrdel stbds_arrdel
-#define arrdeln stbds_arrdeln
-#define arrdelswap stbds_arrdelswap
-#define arrcap stbds_arrcap
-#define arrsetcap stbds_arrsetcap
-
-#define hmput stbds_hmput
-#define hmputs stbds_hmputs
-#define hmget stbds_hmget
-#define hmget_ts stbds_hmget_ts
-#define hmgets stbds_hmgets
-#define hmgetp stbds_hmgetp
-#define hmgetp_ts stbds_hmgetp_ts
-#define hmgetp_null stbds_hmgetp_null
-#define hmgeti stbds_hmgeti
-#define hmgeti_ts stbds_hmgeti_ts
-#define hmdel stbds_hmdel
-#define hmlen stbds_hmlen
-#define hmlenu stbds_hmlenu
-#define hmfree stbds_hmfree
-#define hmdefault stbds_hmdefault
-#define hmdefaults stbds_hmdefaults
-
-#define shput stbds_shput
-#define shputi stbds_shputi
-#define shputs stbds_shputs
-#define shget stbds_shget
-#define shgeti stbds_shgeti
-#define shgets stbds_shgets
-#define shgetp stbds_shgetp
-#define shgetp_null stbds_shgetp_null
-#define shdel stbds_shdel
-#define shlen stbds_shlen
-#define shlenu stbds_shlenu
-#define shfree stbds_shfree
-#define shdefault stbds_shdefault
-#define shdefaults stbds_shdefaults
-#define sh_new_arena stbds_sh_new_arena
-#define sh_new_strdup stbds_sh_new_strdup
-
-#define stralloc stbds_stralloc
-#define strreset stbds_strreset
-#endif
-
-#if defined(STBDS_REALLOC) && !defined(STBDS_FREE) || !defined(STBDS_REALLOC) && defined(STBDS_FREE)
-#error "You must define both STBDS_REALLOC and STBDS_FREE, or neither."
-#endif
-#if !defined(STBDS_REALLOC) && !defined(STBDS_FREE)
-#include
-#define STBDS_REALLOC(c,p,s) realloc(p,s)
-#define STBDS_FREE(c,p) free(p)
-#endif
-
-#ifdef __cplusplus
-extern "C" {
-#endif
-
-// for security against attackers, seed the library with a random number, at least time() but stronger is better
-extern void stbds_rand_seed(size_t seed);
-
-// these are the hash functions used internally if you want to test them or use them for other purposes
-extern size_t stbds_hash_bytes(void *p, size_t len, size_t seed);
-extern size_t stbds_hash_string(char *str, size_t seed);
-
-// this is a simple string arena allocator, initialize with e.g. 'stbds_string_arena my_arena={0}'.
-typedef struct stbds_string_arena stbds_string_arena;
-extern char * stbds_stralloc(stbds_string_arena *a, char *str);
-extern void stbds_strreset(stbds_string_arena *a);
-
-// have to #define STBDS_UNIT_TESTS to call this
-extern void stbds_unit_tests(void);
-
-///////////////
-//
-// Everything below here is implementation details
-//
-
-extern void * stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap);
-extern void stbds_hmfree_func(void *p, size_t elemsize);
-extern void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
-extern void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode);
-extern void * stbds_hmput_default(void *a, size_t elemsize);
-extern void * stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode);
-extern void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode);
-extern void * stbds_shmode_func(size_t elemsize, int mode);
-
-#ifdef __cplusplus
-}
-#endif
-
-#if defined(__GNUC__) || defined(__clang__)
-#define STBDS_HAS_TYPEOF
-#ifdef __cplusplus
-//#define STBDS_HAS_LITERAL_ARRAY // this is currently broken for clang
-#endif
-#endif
-
-#if !defined(__cplusplus)
-#if defined(__STDC_VERSION__) && __STDC_VERSION__ >= 199901L
-#define STBDS_HAS_LITERAL_ARRAY
-#endif
-#endif
-
-// this macro takes the address of the argument, but on gcc/clang can accept rvalues
-#if defined(STBDS_HAS_LITERAL_ARRAY) && defined(STBDS_HAS_TYPEOF)
- #if __clang__
- #define STBDS_ADDRESSOF(typevar, value) ((__typeof__(typevar)[1]){value}) // literal array decays to pointer to value
- #else
- #define STBDS_ADDRESSOF(typevar, value) ((typeof(typevar)[1]){value}) // literal array decays to pointer to value
- #endif
-#else
-#define STBDS_ADDRESSOF(typevar, value) &(value)
-#endif
-
-#define STBDS_OFFSETOF(var,field) ((char *) &(var)->field - (char *) (var))
-
-#define stbds_header(t) ((stbds_array_header *) (t) - 1)
-#define stbds_temp(t) stbds_header(t)->temp
-
-#define stbds_arrsetcap(a,n) (stbds_arrgrow(a,0,n))
-#define stbds_arrsetlen(a,n) ((stbds_arrcap(a) < (size_t) (n) ? stbds_arrsetcap((a),(size_t)(n)),0 : 0), (a) ? stbds_header(a)->length = (size_t) (n) : 0)
-#define stbds_arrcap(a) ((a) ? stbds_header(a)->capacity : 0)
-#define stbds_arrlen(a) ((a) ? (ptrdiff_t) stbds_header(a)->length : 0)
-#define stbds_arrlenu(a) ((a) ? stbds_header(a)->length : 0)
-#define stbds_arrput(a,v) (stbds_arrmaybegrow(a,1), (a)[stbds_header(a)->length++] = (v))
-#define stbds_arrpush stbds_arrput // synonym
-#define stbds_arrpop(a) (stbds_header(a)->length--, (a)[stbds_header(a)->length])
-#define stbds_arraddn(a,n) (stbds_arrmaybegrow(a,n), stbds_header(a)->length += (n), stbds_header(a)->length-(n))
-#define stbds_arrlast(a) ((a)[stbds_header(a)->length-1])
-#define stbds_arrfree(a) ((void) ((a) ? STBDS_FREE(NULL,stbds_header(a)) : (void)0), (a)=NULL)
-#define stbds_arrdel(a,i) stbds_arrdeln(a,i,1)
-#define stbds_arrdeln(a,i,n) (memmove(&(a)[i], &(a)[(i)+(n)], sizeof *(a) * (stbds_header(a)->length-(n)-(i))), stbds_header(a)->length -= (n))
-#define stbds_arrdelswap(a,i) ((a)[i] = stbds_arrlast(a), stbds_header(a)->length -= 1)
-#define stbds_arrinsn(a,i,n) (stbds_arraddn((a),(n)), memmove(&(a)[(i)+(n)], &(a)[i], sizeof *(a) * (stbds_header(a)->length-(n)-(i))))
-#define stbds_arrins(a,i,v) (stbds_arrinsn((a),(i),1), (a)[i]=(v))
-
-#define stbds_arrmaybegrow(a,n) ((!(a) || stbds_header(a)->length + (n) > stbds_header(a)->capacity) \
- ? (stbds_arrgrow(a,n,0),0) : 0)
-
-#define stbds_arrgrow(a,b,c) ((a) = stbds_arrgrowf_wrapper((a), sizeof *(a), (b), (c)))
-
-#define stbds_hmput(t, k, v) \
- ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, 0), \
- (t)[stbds_temp((t)-1)].key = (k), \
- (t)[stbds_temp((t)-1)].value = (v))
-
-#define stbds_hmputs(t, s) \
- ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), &(s).key, sizeof (s).key, STBDS_HM_BINARY), \
- (t)[stbds_temp((t)-1)] = (s))
-
-#define stbds_hmgeti(t,k) \
- ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_HM_BINARY), \
- stbds_temp((t)-1))
-
-#define stbds_hmgeti_ts(t,k,temp) \
- ((t) = stbds_hmget_key_ts_wrapper((t), sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, &(temp), STBDS_HM_BINARY), \
- (temp))
-
-#define stbds_hmgetp(t, k) \
- ((void) stbds_hmgeti(t,k), &(t)[stbds_temp((t)-1)])
-
-#define stbds_hmgetp_ts(t, k, temp) \
- ((void) stbds_hmgeti_ts(t,k,temp), &(t)[temp])
-
-#define stbds_hmdel(t,k) \
- (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) STBDS_ADDRESSOF((t)->key, (k)), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_BINARY)),(t)?stbds_temp((t)-1):0)
-
-#define stbds_hmdefault(t, v) \
- ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1].value = (v))
-
-#define stbds_hmdefaults(t, s) \
- ((t) = stbds_hmput_default_wrapper((t), sizeof *(t)), (t)[-1] = (s))
-
-#define stbds_hmfree(p) \
- ((void) ((p) != NULL ? stbds_hmfree_func((p)-1,sizeof*(p)),0 : 0),(p)=NULL)
-
-#define stbds_hmgets(t, k) (*stbds_hmgetp(t,k))
-#define stbds_hmget(t, k) (stbds_hmgetp(t,k)->value)
-#define stbds_hmget_ts(t, k, temp) (stbds_hmgetp_ts(t,k,temp)->value)
-#define stbds_hmlen(t) ((t) ? (ptrdiff_t) stbds_header((t)-1)->length-1 : 0)
-#define stbds_hmlenu(t) ((t) ? stbds_header((t)-1)->length-1 : 0)
-#define stbds_hmgetp_null(t,k) (stbds_hmgeti(t,k) == -1 ? NULL : &(t)[stbds_temp(t)-1])
-
-#define stbds_shput(t, k, v) \
- ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
- (t)[stbds_temp((t)-1)].value = (v))
-
-#define stbds_shputi(t, k, v) \
- ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
- (t)[stbds_temp((t)-1)].value = (v), stbds_temp((t)-1))
-
-#define stbds_shputs(t, s) \
- ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (s).key, sizeof (s).key, STBDS_HM_STRING), \
- (t)[stbds_temp((t)-1)] = (s))
-
-#define stbds_pshput(t, p) \
- ((t) = stbds_hmput_key_wrapper((t), sizeof *(t), (void*) (p)->key, sizeof (p)->key, STBDS_HM_PTR_TO_STRING), \
- (t)[stbds_temp((t)-1)] = (p))
-
-#define stbds_shgeti(t,k) \
- ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_HM_STRING), \
- stbds_temp((t)-1))
-
-#define stbds_pshgeti(t,k) \
- ((t) = stbds_hmget_key_wrapper((t), sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_HM_PTR_TO_STRING), \
- stbds_temp((t)-1))
-
-#define stbds_shgetp(t, k) \
- ((void) stbds_shgeti(t,k), &(t)[stbds_temp((t)-1)])
-
-#define stbds_pshget(t, k) \
- ((void) stbds_pshgeti(t,k), (t)[stbds_temp((t)-1)])
-
-#define stbds_shdel(t,k) \
- (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (t)->key, STBDS_OFFSETOF((t),key), STBDS_HM_STRING)),(t)?stbds_temp((t)-1):0)
-#define stbds_pshdel(t,k) \
- (((t) = stbds_hmdel_key_wrapper((t),sizeof *(t), (void*) (k), sizeof (*(t))->key, STBDS_OFFSETOF(*(t),key), STBDS_HM_PTR_TO_STRING)),(t)?stbds_temp((t)-1):0)
-
-#define stbds_sh_new_arena(t) \
- ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_ARENA))
-#define stbds_sh_new_strdup(t) \
- ((t) = stbds_shmode_func_wrapper(t, sizeof *(t), STBDS_SH_STRDUP))
-
-#define stbds_shdefault(t, v) stbds_hmdefault(t,v)
-#define stbds_shdefaults(t, s) stbds_hmdefaults(t,s)
-
-#define stbds_shfree stbds_hmfree
-#define stbds_shlenu stbds_hmlenu
-
-#define stbds_shgets(t, k) (*stbds_shgetp(t,k))
-#define stbds_shget(t, k) (stbds_shgetp(t,k)->value)
-#define stbds_shgetp_null(t,k) (stbds_shgeti(t,k) == -1 ? NULL : &(t)[stbds_temp(t)-1])
-#define stbds_shlen stbds_hmlen
-
-typedef struct
-{
- size_t length;
- size_t capacity;
- void * hash_table;
- ptrdiff_t temp;
-} stbds_array_header;
-
-typedef struct stbds_string_block
-{
- struct stbds_string_block *next;
- char storage[8];
-} stbds_string_block;
-
-struct stbds_string_arena
-{
- stbds_string_block *storage;
- size_t remaining;
- unsigned char block;
- unsigned char mode; // this isn't used by the string arena itself
-};
-
-#define STBDS_HM_BINARY 0
-#define STBDS_HM_STRING 1
-
-enum
-{
- STBDS_SH_NONE,
- STBDS_SH_DEFAULT,
- STBDS_SH_STRDUP,
- STBDS_SH_ARENA
-};
-
-#ifdef __cplusplus
-// in C we use implicit assignment from these void*-returning functions to T*.
-// in C++ these templates make the same code work
-template static T * stbds_arrgrowf_wrapper(T *a, size_t elemsize, size_t addlen, size_t min_cap) {
- return (T*)stbds_arrgrowf((void *)a, elemsize, addlen, min_cap);
-}
-template static T * stbds_hmget_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) {
- return (T*)stbds_hmget_key((void*)a, elemsize, key, keysize, mode);
-}
-template static T * stbds_hmget_key_ts_wrapper(T *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode) {
- return (T*)stbds_hmget_key_ts((void*)a, elemsize, key, keysize, temp, mode);
-}
-template static T * stbds_hmput_default_wrapper(T *a, size_t elemsize) {
- return (T*)stbds_hmput_default((void *)a, elemsize);
-}
-template static T * stbds_hmput_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, int mode) {
- return (T*)stbds_hmput_key((void*)a, elemsize, key, keysize, mode);
-}
-template static T * stbds_hmdel_key_wrapper(T *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode){
- return (T*)stbds_hmdel_key((void*)a, elemsize, key, keysize, keyoffset, mode);
-}
-template static T * stbds_shmode_func_wrapper(T *, size_t elemsize, int mode) {
- return (T*)stbds_shmode_func(elemsize, mode);
-}
-#else
-#define stbds_arrgrowf_wrapper stbds_arrgrowf
-#define stbds_hmget_key_wrapper stbds_hmget_key
-#define stbds_hmget_key_ts_wrapper stbds_hmget_key_ts
-#define stbds_hmput_default_wrapper stbds_hmput_default
-#define stbds_hmput_key_wrapper stbds_hmput_key
-#define stbds_hmdel_key_wrapper stbds_hmdel_key
-#define stbds_shmode_func_wrapper(t,e,m) stbds_shmode_func(e,m)
-#endif
-
-#endif // INCLUDE_STB_DS_H
-
-
-//////////////////////////////////////////////////////////////////////////////
-//
-// IMPLEMENTATION
-//
-
-#ifdef STB_DS_IMPLEMENTATION
-#include
-#include
-
-#ifndef STBDS_ASSERT
-#define STBDS_ASSERT_WAS_UNDEFINED
-#define STBDS_ASSERT(x) ((void) 0)
-#endif
-
-#ifdef STBDS_STATISTICS
-#define STBDS_STATS(x) x
-size_t stbds_array_grow;
-size_t stbds_hash_grow;
-size_t stbds_hash_shrink;
-size_t stbds_hash_rebuild;
-size_t stbds_hash_probes;
-size_t stbds_hash_alloc;
-size_t stbds_rehash_probes;
-size_t stbds_rehash_items;
-#else
-#define STBDS_STATS(x)
-#endif
-
-//
-// stbds_arr implementation
-//
-
-//int *prev_allocs[65536];
-//int num_prev;
-
-void *stbds_arrgrowf(void *a, size_t elemsize, size_t addlen, size_t min_cap)
-{
- void *b;
- size_t min_len = stbds_arrlen(a) + addlen;
-
- // compute the minimum capacity needed
- if (min_len > min_cap)
- min_cap = min_len;
-
- if (min_cap <= stbds_arrcap(a))
- return a;
-
- // increase needed capacity to guarantee O(1) amortized
- if (min_cap < 2 * stbds_arrcap(a))
- min_cap = 2 * stbds_arrcap(a);
- else if (min_cap < 4)
- min_cap = 4;
-
- //if (num_prev < 65536) if (a) prev_allocs[num_prev++] = (int *) ((char *) a+1);
- //if (num_prev == 2201)
- // num_prev = num_prev;
- b = STBDS_REALLOC(NULL, (a) ? stbds_header(a) : 0, elemsize * min_cap + sizeof(stbds_array_header));
- //if (num_prev < 65536) prev_allocs[num_prev++] = (int *) (char *) b;
- b = (char *) b + sizeof(stbds_array_header);
- if (a == NULL) {
- stbds_header(b)->length = 0;
- stbds_header(b)->hash_table = 0;
- } else {
- STBDS_STATS(++stbds_array_grow);
- }
- stbds_header(b)->capacity = min_cap;
-
- return b;
-}
-
-//
-// stbds_hm hash table implementation
-//
-
-#ifdef STBDS_INTERNAL_SMALL_BUCKET
-#define STBDS_BUCKET_LENGTH 4
-#else
-#define STBDS_BUCKET_LENGTH 8
-#endif
-
-#define STBDS_BUCKET_SHIFT (STBDS_BUCKET_LENGTH == 8 ? 3 : 2)
-#define STBDS_BUCKET_MASK (STBDS_BUCKET_LENGTH-1)
-#define STBDS_CACHE_LINE_SIZE 64
-
-#define STBDS_ALIGN_FWD(n,a) (((n) + (a) - 1) & ~((a)-1))
-
-typedef struct
-{
- size_t hash [STBDS_BUCKET_LENGTH];
- ptrdiff_t index[STBDS_BUCKET_LENGTH];
-} stbds_hash_bucket; // in 32-bit, this is one 64-byte cache line; in 64-bit, each array is one 64-byte cache line
-
-typedef struct
-{
- size_t slot_count;
- size_t used_count;
- size_t used_count_threshold;
- size_t used_count_shrink_threshold;
- size_t tombstone_count;
- size_t tombstone_count_threshold;
- size_t seed;
- size_t slot_count_log2;
- stbds_string_arena string;
- stbds_hash_bucket *storage; // not a separate allocation, just 64-byte aligned storage after this struct
-} stbds_hash_index;
-
-#define STBDS_INDEX_EMPTY -1
-#define STBDS_INDEX_DELETED -2
-#define STBDS_INDEX_IN_USE(x) ((x) >= 0)
-
-#define STBDS_HASH_EMPTY 0
-#define STBDS_HASH_DELETED 1
-
-static size_t stbds_hash_seed=0x31415926;
-
-void stbds_rand_seed(size_t seed)
-{
- stbds_hash_seed = seed;
-}
-
-#define stbds_load_32_or_64(var, temp, v32, v64_hi, v64_lo) \
- temp = v64_lo ^ v32, temp <<= 16, temp <<= 16, temp >>= 16, temp >>= 16, /* discard if 32-bit */ \
- var = v64_hi, var <<= 16, var <<= 16, /* discard if 32-bit */ \
- var ^= temp ^ v32
-
-#define STBDS_SIZE_T_BITS ((sizeof (size_t)) * 8)
-
-static size_t stbds_probe_position(size_t hash, size_t slot_count, size_t slot_log2)
-{
- size_t pos;
- pos = hash & (slot_count-1);
- #ifdef STBDS_INTERNAL_BUCKET_START
- pos &= ~STBDS_BUCKET_MASK;
- #endif
- return pos;
-}
-
-static size_t stbds_log2(size_t slot_count)
-{
- size_t n=0;
- while (slot_count > 1) {
- slot_count >>= 1;
- ++n;
- }
- return n;
-}
-
-static stbds_hash_index *stbds_make_hash_index(size_t slot_count, stbds_hash_index *ot)
-{
- stbds_hash_index *t;
- t = (stbds_hash_index *) STBDS_REALLOC(NULL,0,(slot_count >> STBDS_BUCKET_SHIFT) * sizeof(stbds_hash_bucket) + sizeof(stbds_hash_index) + STBDS_CACHE_LINE_SIZE-1);
- t->storage = (stbds_hash_bucket *) STBDS_ALIGN_FWD((size_t) (t+1), STBDS_CACHE_LINE_SIZE);
- t->slot_count = slot_count;
- t->slot_count_log2 = stbds_log2(slot_count);
- t->tombstone_count = 0;
- t->used_count = 0;
-
- #if 0 // A1
- t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow
- t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild
- t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink
- #elif 1 // A2
- //t->used_count_threshold = slot_count*12/16; // if 12/16th of table is occupied, grow
- //t->tombstone_count_threshold = slot_count* 3/16; // if tombstones are 3/16th of table, rebuild
- //t->used_count_shrink_threshold = slot_count* 4/16; // if table is only 4/16th full, shrink
-
- // compute without overflowing
- t->used_count_threshold = slot_count - (slot_count>>2);
- t->tombstone_count_threshold = (slot_count>>3) + (slot_count>>4);
- t->used_count_shrink_threshold = slot_count >> 2;
-
- #elif 0 // B1
- t->used_count_threshold = slot_count*13/16; // if 13/16th of table is occupied, grow
- t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild
- t->used_count_shrink_threshold = slot_count* 5/16; // if table is only 5/16th full, shrink
- #else // C1
- t->used_count_threshold = slot_count*14/16; // if 14/16th of table is occupied, grow
- t->tombstone_count_threshold = slot_count* 2/16; // if tombstones are 2/16th of table, rebuild
- t->used_count_shrink_threshold = slot_count* 6/16; // if table is only 6/16th full, shrink
- #endif
- // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2
- // Note that the larger tables have high variance as they were run fewer times
- // A1 A2 B1 C1
- // 0.10ms : 0.10ms : 0.10ms : 0.11ms : 2,000 inserts creating 2K table
- // 0.96ms : 0.95ms : 0.97ms : 1.04ms : 20,000 inserts creating 20K table
- // 14.48ms : 14.46ms : 10.63ms : 11.00ms : 200,000 inserts creating 200K table
- // 195.74ms : 196.35ms : 203.69ms : 214.92ms : 2,000,000 inserts creating 2M table
- // 2193.88ms : 2209.22ms : 2285.54ms : 2437.17ms : 20,000,000 inserts creating 20M table
- // 65.27ms : 53.77ms : 65.33ms : 65.47ms : 500,000 inserts & deletes in 2K table
- // 72.78ms : 62.45ms : 71.95ms : 72.85ms : 500,000 inserts & deletes in 20K table
- // 89.47ms : 77.72ms : 96.49ms : 96.75ms : 500,000 inserts & deletes in 200K table
- // 97.58ms : 98.14ms : 97.18ms : 97.53ms : 500,000 inserts & deletes in 2M table
- // 118.61ms : 119.62ms : 120.16ms : 118.86ms : 500,000 inserts & deletes in 20M table
- // 192.11ms : 194.39ms : 196.38ms : 195.73ms : 500,000 inserts & deletes in 200M table
-
- if (slot_count <= STBDS_BUCKET_LENGTH)
- t->used_count_shrink_threshold = 0;
- // to avoid infinite loop, we need to guarantee that at least one slot is empty and will terminate probes
- STBDS_ASSERT(t->used_count_threshold + t->tombstone_count_threshold < t->slot_count);
- STBDS_STATS(++stbds_hash_alloc);
- if (ot) {
- t->string = ot->string;
- // reuse old seed so we can reuse old hashes so below "copy out old data" doesn't do any hashing
- t->seed = ot->seed;
- } else {
- size_t a,b,temp;
- memset(&t->string, 0, sizeof(t->string));
- t->seed = stbds_hash_seed;
- // LCG
- // in 32-bit, a = 2147001325 b = 715136305
- // in 64-bit, a = 2862933555777941757 b = 3037000493
- stbds_load_32_or_64(a,temp, 2147001325, 0x27bb2ee6, 0x87b0b0fd);
- stbds_load_32_or_64(b,temp, 715136305, 0, 0xb504f32d);
- stbds_hash_seed = stbds_hash_seed * a + b;
- }
-
- {
- size_t i,j;
- for (i=0; i < slot_count >> STBDS_BUCKET_SHIFT; ++i) {
- stbds_hash_bucket *b = &t->storage[i];
- for (j=0; j < STBDS_BUCKET_LENGTH; ++j)
- b->hash[j] = STBDS_HASH_EMPTY;
- for (j=0; j < STBDS_BUCKET_LENGTH; ++j)
- b->index[j] = STBDS_INDEX_EMPTY;
- }
- }
-
- // copy out the old data, if any
- if (ot) {
- size_t i,j;
- t->used_count = ot->used_count;
- for (i=0; i < ot->slot_count >> STBDS_BUCKET_SHIFT; ++i) {
- stbds_hash_bucket *ob = &ot->storage[i];
- for (j=0; j < STBDS_BUCKET_LENGTH; ++j) {
- if (STBDS_INDEX_IN_USE(ob->index[j])) {
- size_t hash = ob->hash[j];
- size_t pos = stbds_probe_position(hash, t->slot_count, t->slot_count_log2);
- size_t step = STBDS_BUCKET_LENGTH;
- STBDS_STATS(++stbds_rehash_items);
- for (;;) {
- size_t limit,z;
- stbds_hash_bucket *bucket;
- bucket = &t->storage[pos >> STBDS_BUCKET_SHIFT];
- STBDS_STATS(++stbds_rehash_probes);
-
- for (z=pos & STBDS_BUCKET_MASK; z < STBDS_BUCKET_LENGTH; ++z) {
- if (bucket->hash[z] == 0) {
- bucket->hash[z] = hash;
- bucket->index[z] = ob->index[j];
- goto done;
- }
- }
-
- limit = pos & STBDS_BUCKET_MASK;
- for (z = 0; z < limit; ++z) {
- if (bucket->hash[z] == 0) {
- bucket->hash[z] = hash;
- bucket->index[z] = ob->index[j];
- goto done;
- }
- }
-
- pos += step; // quadratic probing
- step += STBDS_BUCKET_LENGTH;
- pos &= (t->slot_count-1);
- }
- }
- done:
- ;
- }
- }
- }
-
- return t;
-}
-
-#define STBDS_ROTATE_LEFT(val, n) (((val) << (n)) | ((val) >> (STBDS_SIZE_T_BITS - (n))))
-#define STBDS_ROTATE_RIGHT(val, n) (((val) >> (n)) | ((val) << (STBDS_SIZE_T_BITS - (n))))
-
-size_t stbds_hash_string(char *str, size_t seed)
-{
- size_t hash = seed;
- while (*str)
- hash = STBDS_ROTATE_LEFT(hash, 9) + (unsigned char) *str++;
-
- // Thomas Wang 64-to-32 bit mix function, hopefully also works in 32 bits
- hash ^= seed;
- hash = (~hash) + (hash << 18);
- hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,31);
- hash = hash * 21;
- hash ^= hash ^ STBDS_ROTATE_RIGHT(hash,11);
- hash += (hash << 6);
- hash ^= STBDS_ROTATE_RIGHT(hash,22);
- return hash+seed;
-}
-
-#ifdef STBDS_SIPHASH_2_4
-#define STBDS_SIPHASH_C_ROUNDS 2
-#define STBDS_SIPHASH_D_ROUNDS 4
-typedef int STBDS_SIPHASH_2_4_can_only_be_used_in_64_bit_builds[sizeof(size_t) == 8 ? 1 : -1];
-#endif
-
-#ifndef STBDS_SIPHASH_C_ROUNDS
-#define STBDS_SIPHASH_C_ROUNDS 1
-#endif
-#ifndef STBDS_SIPHASH_D_ROUNDS
-#define STBDS_SIPHASH_D_ROUNDS 1
-#endif
-
-#ifdef _MSC_VER
-#pragma warning(push)
-#pragma warning(disable:4127) // conditional expression is constant, for do..while(0) and sizeof()==
-#endif
-
-static size_t stbds_siphash_bytes(void *p, size_t len, size_t seed)
-{
- unsigned char *d = (unsigned char *) p;
- size_t i,j;
- size_t v0,v1,v2,v3, data;
-
- // hash that works on 32- or 64-bit registers without knowing which we have
- // (computes different results on 32-bit and 64-bit platform)
- // derived from siphash, but on 32-bit platforms very different as it uses 4 32-bit state not 4 64-bit
- v0 = ((((size_t) 0x736f6d65 << 16) << 16) + 0x70736575) ^ seed;
- v1 = ((((size_t) 0x646f7261 << 16) << 16) + 0x6e646f6d) ^ ~seed;
- v2 = ((((size_t) 0x6c796765 << 16) << 16) + 0x6e657261) ^ seed;
- v3 = ((((size_t) 0x74656462 << 16) << 16) + 0x79746573) ^ ~seed;
-
- #ifdef STBDS_TEST_SIPHASH_2_4
- // hardcoded with key material in the siphash test vectors
- v0 ^= 0x0706050403020100ull ^ seed;
- v1 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed;
- v2 ^= 0x0706050403020100ull ^ seed;
- v3 ^= 0x0f0e0d0c0b0a0908ull ^ ~seed;
- #endif
-
- #define STBDS_SIPROUND() \
- do { \
- v0 += v1; v1 = STBDS_ROTATE_LEFT(v1, 13); v1 ^= v0; v0 = STBDS_ROTATE_LEFT(v0,STBDS_SIZE_T_BITS/2); \
- v2 += v3; v3 = STBDS_ROTATE_LEFT(v3, 16); v3 ^= v2; \
- v2 += v1; v1 = STBDS_ROTATE_LEFT(v1, 17); v1 ^= v2; v2 = STBDS_ROTATE_LEFT(v2,STBDS_SIZE_T_BITS/2); \
- v0 += v3; v3 = STBDS_ROTATE_LEFT(v3, 21); v3 ^= v0; \
- } while (0)
-
- for (i=0; i+sizeof(size_t) <= len; i += sizeof(size_t), d += sizeof(size_t)) {
- data = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
- data |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // discarded if size_t == 4
-
- v3 ^= data;
- for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j)
- STBDS_SIPROUND();
- v0 ^= data;
- }
- data = len << (STBDS_SIZE_T_BITS-8);
- switch (len - i) {
- case 7: data |= ((size_t) d[6] << 24) << 24; // fall through
- case 6: data |= ((size_t) d[5] << 20) << 20; // fall through
- case 5: data |= ((size_t) d[4] << 16) << 16; // fall through
- case 4: data |= (d[3] << 24); // fall through
- case 3: data |= (d[2] << 16); // fall through
- case 2: data |= (d[1] << 8); // fall through
- case 1: data |= d[0]; // fall through
- case 0: break;
- }
- v3 ^= data;
- for (j=0; j < STBDS_SIPHASH_C_ROUNDS; ++j)
- STBDS_SIPROUND();
- v0 ^= data;
- v2 ^= 0xff;
- for (j=0; j < STBDS_SIPHASH_D_ROUNDS; ++j)
- STBDS_SIPROUND();
-
-#ifdef STBDS_SIPHASH_2_4
- return v0^v1^v2^v3;
-#else
- return v1^v2^v3; // slightly stronger since v0^v3 in above cancels out final round operation? I tweeted at the authors of SipHash about this but they didn't reply
-#endif
-}
-
-size_t stbds_hash_bytes(void *p, size_t len, size_t seed)
-{
-#ifdef STBDS_SIPHASH_2_4
- return stbds_siphash_bytes(p,len,seed);
-#else
- unsigned char *d = (unsigned char *) p;
-
- if (len == 4) {
- unsigned int hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
- #if 0
- // HASH32-A Bob Jenkin's hash function w/o large constants
- hash ^= seed;
- hash -= (hash<<6);
- hash ^= (hash>>17);
- hash -= (hash<<9);
- hash ^= seed;
- hash ^= (hash<<4);
- hash -= (hash<<3);
- hash ^= (hash<<10);
- hash ^= (hash>>15);
- #elif 1
- // HASH32-BB Bob Jenkin's presumably-accidental version of Thomas Wang hash with rotates turned into shifts.
- // Note that converting these back to rotates makes it run a lot slower, presumably due to collisions, so I'm
- // not really sure what's going on.
- hash ^= seed;
- hash = (hash ^ 61) ^ (hash >> 16);
- hash = hash + (hash << 3);
- hash = hash ^ (hash >> 4);
- hash = hash * 0x27d4eb2d;
- hash ^= seed;
- hash = hash ^ (hash >> 15);
- #else // HASH32-C - Murmur3
- hash ^= seed;
- hash *= 0xcc9e2d51;
- hash = (hash << 17) | (hash >> 15);
- hash *= 0x1b873593;
- hash ^= seed;
- hash = (hash << 19) | (hash >> 13);
- hash = hash*5 + 0xe6546b64;
- hash ^= hash >> 16;
- hash *= 0x85ebca6b;
- hash ^= seed;
- hash ^= hash >> 13;
- hash *= 0xc2b2ae35;
- hash ^= hash >> 16;
- #endif
- // Following statistics were measured on a Core i7-6700 @ 4.00Ghz, compiled with clang 7.0.1 -O2
- // Note that the larger tables have high variance as they were run fewer times
- // HASH32-A // HASH32-BB // HASH32-C
- // 0.10ms // 0.10ms // 0.10ms : 2,000 inserts creating 2K table
- // 0.96ms // 0.95ms // 0.99ms : 20,000 inserts creating 20K table
- // 14.69ms // 14.43ms // 14.97ms : 200,000 inserts creating 200K table
- // 199.99ms // 195.36ms // 202.05ms : 2,000,000 inserts creating 2M table
- // 2234.84ms // 2187.74ms // 2240.38ms : 20,000,000 inserts creating 20M table
- // 55.68ms // 53.72ms // 57.31ms : 500,000 inserts & deletes in 2K table
- // 63.43ms // 61.99ms // 65.73ms : 500,000 inserts & deletes in 20K table
- // 80.04ms // 77.96ms // 81.83ms : 500,000 inserts & deletes in 200K table
- // 100.42ms // 97.40ms // 102.39ms : 500,000 inserts & deletes in 2M table
- // 119.71ms // 120.59ms // 121.63ms : 500,000 inserts & deletes in 20M table
- // 185.28ms // 195.15ms // 187.74ms : 500,000 inserts & deletes in 200M table
- // 15.58ms // 14.79ms // 15.52ms : 200,000 inserts creating 200K table with varying key spacing
-
- return (((size_t) hash << 16 << 16) | hash) ^ seed;
- } else if (len == 8 && sizeof(size_t) == 8) {
- size_t hash = d[0] | (d[1] << 8) | (d[2] << 16) | (d[3] << 24);
- hash |= (size_t) (d[4] | (d[5] << 8) | (d[6] << 16) | (d[7] << 24)) << 16 << 16; // avoid warning if size_t == 4
- hash ^= seed;
- hash = (~hash) + (hash << 21);
- hash ^= STBDS_ROTATE_RIGHT(hash,24);
- hash *= 265;
- hash ^= STBDS_ROTATE_RIGHT(hash,14);
- hash ^= seed;
- hash *= 21;
- hash ^= STBDS_ROTATE_RIGHT(hash,28);
- hash += (hash << 31);
- hash = (~hash) + (hash << 18);
- return hash;
- } else {
- return stbds_siphash_bytes(p,len,seed);
- }
-#endif
-}
-#ifdef _MSC_VER
-#pragma warning(pop)
-#endif
-
-
-static int stbds_is_key_equal(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode, size_t i)
-{
- if (mode >= STBDS_HM_STRING)
- return 0==strcmp((char *) key, * (char **) ((char *) a + elemsize*i));
- else
- return 0==memcmp(key, (char *) a + elemsize*i, keysize);
-}
-
-#define STBDS_HASH_TO_ARR(x,elemsize) ((char*) (x) - (elemsize))
-#define STBDS_ARR_TO_HASH(x,elemsize) ((char*) (x) + (elemsize))
-
-#define stbds_hash_table(a) ((stbds_hash_index *) stbds_header(a)->hash_table)
-
-void stbds_hmfree_func(void *a, size_t elemsize)
-{
- if (a == NULL) return;
- if (stbds_hash_table(a) != NULL) {
- if (stbds_hash_table(a)->string.mode == STBDS_SH_STRDUP) {
- size_t i;
- // skip 0th element, which is default
- for (i=1; i < stbds_header(a)->length; ++i)
- STBDS_FREE(NULL, *(char**) ((char *) a + elemsize*i));
- }
- stbds_strreset(&stbds_hash_table(a)->string);
- }
- STBDS_FREE(NULL, stbds_header(a)->hash_table);
- STBDS_FREE(NULL, stbds_header(a));
-}
-
-static ptrdiff_t stbds_hm_find_slot(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode)
-{
- void *raw_a = STBDS_HASH_TO_ARR(a,elemsize);
- stbds_hash_index *table = stbds_hash_table(raw_a);
- size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed);
- size_t step = STBDS_BUCKET_LENGTH;
- size_t limit,i;
- size_t pos;
- stbds_hash_bucket *bucket;
-
- if (hash < 2) hash += 2; // stored hash values are forbidden from being 0, so we can detect empty slots
-
- pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2);
-
- for (;;) {
- STBDS_STATS(++stbds_hash_probes);
- bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT];
-
- // start searching from pos to end of bucket, this should help performance on small hash tables that fit in cache
- for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) {
- if (bucket->hash[i] == hash) {
- if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
- return (pos & ~STBDS_BUCKET_MASK)+i;
- }
- } else if (bucket->hash[i] == STBDS_HASH_EMPTY) {
- return -1;
- }
- }
-
- // search from beginning of bucket to pos
- limit = pos & STBDS_BUCKET_MASK;
- for (i = 0; i < limit; ++i) {
- if (bucket->hash[i] == hash) {
- if (stbds_is_key_equal(a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
- return (pos & ~STBDS_BUCKET_MASK)+i;
- }
- } else if (bucket->hash[i] == STBDS_HASH_EMPTY) {
- return -1;
- }
- }
-
- // quadratic probing
- pos += step;
- step += STBDS_BUCKET_LENGTH;
- pos &= (table->slot_count-1);
- }
- /* NOTREACHED */
-}
-
-void * stbds_hmget_key_ts(void *a, size_t elemsize, void *key, size_t keysize, ptrdiff_t *temp, int mode)
-{
- size_t keyoffset = 0;
- if (a == NULL) {
- // make it non-empty so we can return a temp
- a = stbds_arrgrowf(0, elemsize, 0, 1);
- stbds_header(a)->length += 1;
- memset(a, 0, elemsize);
- *temp = STBDS_INDEX_EMPTY;
- // adjust a to point after the default element
- return STBDS_ARR_TO_HASH(a,elemsize);
- } else {
- stbds_hash_index *table;
- void *raw_a = STBDS_HASH_TO_ARR(a,elemsize);
- // adjust a to point to the default element
- table = (stbds_hash_index *) stbds_header(raw_a)->hash_table;
- if (table == 0) {
- *temp = -1;
- } else {
- ptrdiff_t slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode);
- if (slot < 0) {
- *temp = STBDS_INDEX_EMPTY;
- } else {
- stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT];
- *temp = b->index[slot & STBDS_BUCKET_MASK];
- }
- }
- return a;
- }
-}
-
-void * stbds_hmget_key(void *a, size_t elemsize, void *key, size_t keysize, int mode)
-{
- ptrdiff_t temp;
- void *p = stbds_hmget_key_ts(a, elemsize, key, keysize, &temp, mode);
- stbds_temp(STBDS_HASH_TO_ARR(p,elemsize)) = temp;
- return p;
-}
-
-void * stbds_hmput_default(void *a, size_t elemsize)
-{
- // three cases:
- // a is NULL <- allocate
- // a has a hash table but no entries, because of shmode <- grow
- // a has entries <- do nothing
- if (a == NULL || stbds_header(STBDS_HASH_TO_ARR(a,elemsize))->length == 0) {
- a = stbds_arrgrowf(a ? STBDS_HASH_TO_ARR(a,elemsize) : NULL, elemsize, 0, 1);
- stbds_header(a)->length += 1;
- memset(a, 0, elemsize);
- a=STBDS_ARR_TO_HASH(a,elemsize);
- }
- return a;
-}
-
-static char *stbds_strdup(char *str);
-
-void *stbds_hmput_key(void *a, size_t elemsize, void *key, size_t keysize, int mode)
-{
- size_t keyoffset=0;
- void *raw_a;
- stbds_hash_index *table;
-
- if (a == NULL) {
- a = stbds_arrgrowf(0, elemsize, 0, 1);
- memset(a, 0, elemsize);
- stbds_header(a)->length += 1;
- // adjust a to point AFTER the default element
- a = STBDS_ARR_TO_HASH(a,elemsize);
- }
-
- // adjust a to point to the default element
- raw_a = a;
- a = STBDS_HASH_TO_ARR(a,elemsize);
-
- table = (stbds_hash_index *) stbds_header(a)->hash_table;
-
- if (table == NULL || table->used_count >= table->used_count_threshold) {
- stbds_hash_index *nt;
- size_t slot_count;
-
- slot_count = (table == NULL) ? STBDS_BUCKET_LENGTH : table->slot_count*2;
- nt = stbds_make_hash_index(slot_count, table);
- if (table)
- STBDS_FREE(NULL, table);
- else
- nt->string.mode = mode >= STBDS_HM_STRING ? STBDS_SH_DEFAULT : 0;
- stbds_header(a)->hash_table = table = nt;
- STBDS_STATS(++stbds_hash_grow);
- }
-
- // we iterate hash table explicitly because we want to track if we saw a tombstone
- {
- size_t hash = mode >= STBDS_HM_STRING ? stbds_hash_string((char*)key,table->seed) : stbds_hash_bytes(key, keysize,table->seed);
- size_t step = STBDS_BUCKET_LENGTH;
- size_t limit,i;
- size_t pos;
- ptrdiff_t tombstone = -1;
- stbds_hash_bucket *bucket;
-
- // stored hash values are forbidden from being 0, so we can detect empty slots to early out quickly
- if (hash < 2) hash += 2;
-
- pos = stbds_probe_position(hash, table->slot_count, table->slot_count_log2);
-
- for (;;) {
- STBDS_STATS(++stbds_hash_probes);
- bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT];
-
- // start searching from pos to end of bucket
- for (i=pos & STBDS_BUCKET_MASK; i < STBDS_BUCKET_LENGTH; ++i) {
- if (bucket->hash[i] == hash) {
- if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
- stbds_temp(a) = bucket->index[i];
- return STBDS_ARR_TO_HASH(a,elemsize);
- }
- } else if (bucket->hash[i] == 0) {
- pos = (pos & ~STBDS_BUCKET_MASK) + i;
- goto found_empty_slot;
- } else if (tombstone < 0) {
- if (bucket->index[i] == STBDS_INDEX_DELETED)
- tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i);
- }
- }
-
- // search from beginning of bucket to pos
- limit = pos & STBDS_BUCKET_MASK;
- for (i = 0; i < limit; ++i) {
- if (bucket->hash[i] == hash) {
- if (stbds_is_key_equal(raw_a, elemsize, key, keysize, keyoffset, mode, bucket->index[i])) {
- stbds_temp(a) = bucket->index[i];
- return STBDS_ARR_TO_HASH(a,elemsize);
- }
- } else if (bucket->hash[i] == 0) {
- pos = (pos & ~STBDS_BUCKET_MASK) + i;
- goto found_empty_slot;
- } else if (tombstone < 0) {
- if (bucket->index[i] == STBDS_INDEX_DELETED)
- tombstone = (ptrdiff_t) ((pos & ~STBDS_BUCKET_MASK) + i);
- }
- }
-
- // quadratic probing
- pos += step;
- step += STBDS_BUCKET_LENGTH;
- pos &= (table->slot_count-1);
- }
- found_empty_slot:
- if (tombstone >= 0) {
- pos = tombstone;
- --table->tombstone_count;
- }
- ++table->used_count;
-
- {
- ptrdiff_t i = (ptrdiff_t) stbds_arrlen(a);
- // we want to do stbds_arraddn(1), but we can't use the macros since we don't have something of the right type
- if ((size_t) i+1 > stbds_arrcap(a))
- *(void **) &a = stbds_arrgrowf(a, elemsize, 1, 0);
- raw_a = STBDS_ARR_TO_HASH(a,elemsize);
-
- STBDS_ASSERT((size_t) i+1 <= stbds_arrcap(a));
- stbds_header(a)->length = i+1;
- bucket = &table->storage[pos >> STBDS_BUCKET_SHIFT];
- bucket->hash[pos & STBDS_BUCKET_MASK] = hash;
- bucket->index[pos & STBDS_BUCKET_MASK] = i-1;
- stbds_temp(a) = i-1;
-
- switch (table->string.mode) {
- case STBDS_SH_STRDUP: *(char **) ((char *) a + elemsize*i) = stbds_strdup((char*) key); break;
- case STBDS_SH_ARENA: *(char **) ((char *) a + elemsize*i) = stbds_stralloc(&table->string, (char*)key); break;
- case STBDS_SH_DEFAULT: *(char **) ((char *) a + elemsize*i) = (char *) key; break;
- default: memcpy((char *) a + elemsize*i, key, keysize); break;
- }
- }
- return STBDS_ARR_TO_HASH(a,elemsize);
- }
-}
-
-void * stbds_shmode_func(size_t elemsize, int mode)
-{
- void *a = stbds_arrgrowf(0, elemsize, 0, 1);
- stbds_hash_index *h;
- memset(a, 0, elemsize);
- stbds_header(a)->length = 1;
- stbds_header(a)->hash_table = h = (stbds_hash_index *) stbds_make_hash_index(STBDS_BUCKET_LENGTH, NULL);
- h->string.mode = (unsigned char) mode;
- return STBDS_ARR_TO_HASH(a,elemsize);
-}
-
-void * stbds_hmdel_key(void *a, size_t elemsize, void *key, size_t keysize, size_t keyoffset, int mode)
-{
- if (a == NULL) {
- return 0;
- } else {
- stbds_hash_index *table;
- void *raw_a = STBDS_HASH_TO_ARR(a,elemsize);
- table = (stbds_hash_index *) stbds_header(raw_a)->hash_table;
- stbds_temp(raw_a) = 0;
- if (table == 0) {
- return a;
- } else {
- ptrdiff_t slot;
- slot = stbds_hm_find_slot(a, elemsize, key, keysize, keyoffset, mode);
- if (slot < 0)
- return a;
- else {
- stbds_hash_bucket *b = &table->storage[slot >> STBDS_BUCKET_SHIFT];
- int i = slot & STBDS_BUCKET_MASK;
- ptrdiff_t old_index = b->index[i];
- ptrdiff_t final_index = (ptrdiff_t) stbds_arrlen(raw_a)-1-1; // minus one for the raw_a vs a, and minus one for 'last'
- STBDS_ASSERT(slot < (ptrdiff_t) table->slot_count);
- --table->used_count;
- ++table->tombstone_count;
- stbds_temp(raw_a) = 1;
- STBDS_ASSERT(table->used_count >= 0);
- //STBDS_ASSERT(table->tombstone_count < table->slot_count/4);
- b->hash[i] = STBDS_HASH_DELETED;
- b->index[i] = STBDS_INDEX_DELETED;
-
- if (mode == STBDS_HM_STRING && table->string.mode == STBDS_SH_STRDUP)
- STBDS_FREE(NULL, *(char**) ((char *) a+elemsize*old_index));
-
- // if indices are the same, memcpy is a no-op, but back-pointer-fixup will fail, so skip
- if (old_index != final_index) {
- // swap delete
- memmove((char*) a + elemsize*old_index, (char*) a + elemsize*final_index, elemsize);
-
- // now find the slot for the last element
- if (mode == STBDS_HM_STRING)
- slot = stbds_hm_find_slot(a, elemsize, *(char**) ((char *) a+elemsize*old_index + keyoffset), keysize, keyoffset, mode);
- else
- slot = stbds_hm_find_slot(a, elemsize, (char* ) a+elemsize*old_index + keyoffset, keysize, keyoffset, mode);
- STBDS_ASSERT(slot >= 0);
- b = &table->storage[slot >> STBDS_BUCKET_SHIFT];
- i = slot & STBDS_BUCKET_MASK;
- STBDS_ASSERT(b->index[i] == final_index);
- b->index[i] = old_index;
- }
- stbds_header(raw_a)->length -= 1;
-
- if (table->used_count < table->used_count_shrink_threshold && table->slot_count > STBDS_BUCKET_LENGTH) {
- stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count>>1, table);
- STBDS_FREE(NULL, table);
- STBDS_STATS(++stbds_hash_shrink);
- } else if (table->tombstone_count > table->tombstone_count_threshold) {
- stbds_header(raw_a)->hash_table = stbds_make_hash_index(table->slot_count , table);
- STBDS_FREE(NULL, table);
- STBDS_STATS(++stbds_hash_rebuild);
- }
-
- return a;
- }
- }
- }
- /* NOTREACHED */
-}
-
-static char *stbds_strdup(char *str)
-{
- // to keep replaceable allocator simple, we don't want to use strdup.
- // rolling our own also avoids problem of strdup vs _strdup
- size_t len = strlen(str)+1;
- char *p = (char*) STBDS_REALLOC(NULL, 0, len);
- memmove(p, str, len);
- return p;
-}
-
-#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MIN
-#define STBDS_STRING_ARENA_BLOCKSIZE_MIN 512u
-#endif
-#ifndef STBDS_STRING_ARENA_BLOCKSIZE_MAX
-#define STBDS_STRING_ARENA_BLOCKSIZE_MAX (1u<<20)
-#endif
-
-char *stbds_stralloc(stbds_string_arena *a, char *str)
-{
- char *p;
- size_t len = strlen(str)+1;
- if (len > a->remaining) {
- // compute the next blocksize
- size_t blocksize = a->block;
-
- // size is 512, 512, 1024, 1024, 2048, 2048, 4096, 4096, etc., so that
- // there are log(SIZE) allocations to free when we destroy the table
- blocksize = (size_t) (STBDS_STRING_ARENA_BLOCKSIZE_MIN) << (blocksize>>1);
-
- // if size is under 1M, advance to next blocktype
- if (blocksize < (size_t)(STBDS_STRING_ARENA_BLOCKSIZE_MAX))
- ++a->block;
-
- if (len > blocksize) {
- // if string is larger than blocksize, then just allocate the full size.
- // note that we still advance string_block so block size will continue
- // increasing, so e.g. if somebody only calls this with 1000-long strings,
- // eventually the arena will start doubling and handling those as well
- stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + len);
- memmove(sb->storage, str, len);
- if (a->storage) {
- // insert it after the first element, so that we don't waste the space there
- sb->next = a->storage->next;
- a->storage->next = sb;
- } else {
- sb->next = 0;
- a->storage = sb;
- a->remaining = 0; // this is redundant, but good for clarity
- }
- return sb->storage;
- } else {
- stbds_string_block *sb = (stbds_string_block *) STBDS_REALLOC(NULL, 0, sizeof(*sb)-8 + blocksize);
- sb->next = a->storage;
- a->storage = sb;
- a->remaining = blocksize;
- }
- }
-
- STBDS_ASSERT(len <= a->remaining);
- p = a->storage->storage + a->remaining - len;
- a->remaining -= len;
- memmove(p, str, len);
- return p;
-}
-
-void stbds_strreset(stbds_string_arena *a)
-{
- stbds_string_block *x,*y;
- x = a->storage;
- while (x) {
- y = x->next;
- STBDS_FREE(NULL, x);
- x = y;
- }
- memset(a, 0, sizeof(*a));
-}
-
-#endif
-
-//////////////////////////////////////////////////////////////////////////////
-//
-// UNIT TESTS
-//
-
-#ifdef STBDS_UNIT_TESTS
-#include
-#ifdef STBDS_ASSERT_WAS_UNDEFINED
-#undef STBDS_ASSERT
-#endif
-#ifndef STBDS_ASSERT
-#define STBDS_ASSERT assert
-#include
-#endif
-
-typedef struct { int key,b,c,d; } stbds_struct;
-typedef struct { int key[2],b,c,d; } stbds_struct2;
-
-static char buffer[256];
-char *strkey(int n)
-{
-#if defined(_WIN32) && defined(__STDC_WANT_SECURE_LIB__)
- sprintf_s(buffer, sizeof(buffer), "test_%d", n);
-#else
- sprintf(buffer, "test_%d", n);
-#endif
- return buffer;
-}
-
-void stbds_unit_tests(void)
-{
-#if defined(_MSC_VER) && _MSC_VER <= 1200 && defined(__cplusplus)
- // VC6 C++ doesn't like the template<> trick on unnamed structures, so do nothing!
- STBDS_ASSERT(0);
-#else
- const int testsize = 100000;
- const int testsize2 = testsize/20;
- int *arr=NULL;
- struct { int key; int value; } *intmap = NULL;
- struct { char *key; int value; } *strmap = NULL;
- struct { stbds_struct key; int value; } *map = NULL;
- stbds_struct *map2 = NULL;
- stbds_struct2 *map3 = NULL;
- stbds_string_arena sa = { 0 };
- int key3[2] = { 1,2 };
- ptrdiff_t temp;
-
- int i,j;
-
- STBDS_ASSERT(arrlen(arr)==0);
- for (i=0; i < 20000; i += 50) {
- for (j=0; j < i; ++j)
- arrpush(arr,j);
- arrfree(arr);
- }
-
- for (i=0; i < 4; ++i) {
- arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4);
- arrdel(arr,i);
- arrfree(arr);
- arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4);
- arrdelswap(arr,i);
- arrfree(arr);
- }
-
- for (i=0; i < 5; ++i) {
- arrpush(arr,1); arrpush(arr,2); arrpush(arr,3); arrpush(arr,4);
- stbds_arrins(arr,i,5);
- STBDS_ASSERT(arr[i] == 5);
- if (i < 4)
- STBDS_ASSERT(arr[4] == 4);
- arrfree(arr);
- }
-
- i = 1;
- STBDS_ASSERT(hmgeti(intmap,i) == -1);
- hmdefault(intmap, -2);
- STBDS_ASSERT(hmgeti(intmap, i) == -1);
- STBDS_ASSERT(hmget (intmap, i) == -2);
- for (i=0; i < testsize; i+=2)
- hmput(intmap, i, i*5);
- for (i=0; i < testsize; i+=1) {
- if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 );
- else STBDS_ASSERT(hmget(intmap, i) == i*5);
- if (i & 1) STBDS_ASSERT(hmget_ts(intmap, i, temp) == -2 );
- else STBDS_ASSERT(hmget_ts(intmap, i, temp) == i*5);
- }
- for (i=0; i < testsize; i+=2)
- hmput(intmap, i, i*3);
- for (i=0; i < testsize; i+=1)
- if (i & 1) STBDS_ASSERT(hmget(intmap, i) == -2 );
- else STBDS_ASSERT(hmget(intmap, i) == i*3);
- for (i=2; i < testsize; i+=4)
- hmdel(intmap, i); // delete half the entries
- for (i=0; i < testsize; i+=1)
- if (i & 3) STBDS_ASSERT(hmget(intmap, i) == -2 );
- else STBDS_ASSERT(hmget(intmap, i) == i*3);
- for (i=0; i < testsize; i+=1)
- hmdel(intmap, i); // delete the rest of the entries
- for (i=0; i < testsize; i+=1)
- STBDS_ASSERT(hmget(intmap, i) == -2 );
- hmfree(intmap);
- for (i=0; i < testsize; i+=2)
- hmput(intmap, i, i*3);
- hmfree(intmap);
-
- #if defined(__clang__) || defined(__GNUC__)
- #ifndef __cplusplus
- intmap = NULL;
- hmput(intmap, 15, 7);
- hmput(intmap, 11, 3);
- hmput(intmap, 9, 5);
- STBDS_ASSERT(hmget(intmap, 9) == 5);
- STBDS_ASSERT(hmget(intmap, 11) == 3);
- STBDS_ASSERT(hmget(intmap, 15) == 7);
- #endif
- #endif
-
- for (i=0; i < testsize; ++i)
- stralloc(&sa, strkey(i));
- strreset(&sa);
-
- for (j=0; j < 2; ++j) {
- STBDS_ASSERT(shgeti(strmap,"foo") == -1);
- if (j == 0)
- sh_new_strdup(strmap);
- else
- sh_new_arena(strmap);
- STBDS_ASSERT(shgeti(strmap,"foo") == -1);
- shdefault(strmap, -2);
- STBDS_ASSERT(shgeti(strmap,"foo") == -1);
- for (i=0; i < testsize; i+=2)
- shput(strmap, strkey(i), i*3);
- for (i=0; i < testsize; i+=1)
- if (i & 1) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 );
- else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3);
- for (i=2; i < testsize; i+=4)
- shdel(strmap, strkey(i)); // delete half the entries
- for (i=0; i < testsize; i+=1)
- if (i & 3) STBDS_ASSERT(shget(strmap, strkey(i)) == -2 );
- else STBDS_ASSERT(shget(strmap, strkey(i)) == i*3);
- for (i=0; i < testsize; i+=1)
- shdel(strmap, strkey(i)); // delete the rest of the entries
- for (i=0; i < testsize; i+=1)
- STBDS_ASSERT(shget(strmap, strkey(i)) == -2 );
- shfree(strmap);
- }
-
- {
- struct { char *key; char value; } *hash = NULL;
- char name[4] = "jen";
- shput(hash, "bob" , 'h');
- shput(hash, "sally" , 'e');
- shput(hash, "fred" , 'l');
- shput(hash, "jen" , 'x');
- shput(hash, "doug" , 'o');
-
- shput(hash, name , 'l');
- shfree(hash);
- }
-
- for (i=0; i < testsize; i += 2) {
- stbds_struct s = { i,i*2,i*3,i*4 };
- hmput(map, s, i*5);
- }
-
- for (i=0; i < testsize; i += 1) {
- stbds_struct s = { i,i*2,i*3 ,i*4 };
- stbds_struct t = { i,i*2,i*3+1,i*4 };
- if (i & 1) STBDS_ASSERT(hmget(map, s) == 0);
- else STBDS_ASSERT(hmget(map, s) == i*5);
- if (i & 1) STBDS_ASSERT(hmget_ts(map, s, temp) == 0);
- else STBDS_ASSERT(hmget_ts(map, s, temp) == i*5);
- //STBDS_ASSERT(hmget(map, t.key) == 0);
- }
-
- for (i=0; i < testsize; i += 2) {
- stbds_struct s = { i,i*2,i*3,i*4 };
- hmputs(map2, s);
- }
- hmfree(map);
-
- for (i=0; i < testsize; i += 1) {
- stbds_struct s = { i,i*2,i*3,i*4 };
- stbds_struct t = { i,i*2,i*3+1,i*4 };
- if (i & 1) STBDS_ASSERT(hmgets(map2, s.key).d == 0);
- else STBDS_ASSERT(hmgets(map2, s.key).d == i*4);
- //STBDS_ASSERT(hmgetp(map2, t.key) == 0);
- }
- hmfree(map2);
-
- for (i=0; i < testsize; i += 2) {
- stbds_struct2 s = { { i,i*2 }, i*3,i*4, i*5 };
- hmputs(map3, s);
- }
- for (i=0; i < testsize; i += 1) {
- stbds_struct2 s = { { i,i*2}, i*3, i*4, i*5 };
- stbds_struct2 t = { { i,i*2}, i*3+1, i*4, i*5 };
- if (i & 1) STBDS_ASSERT(hmgets(map3, s.key).d == 0);
- else STBDS_ASSERT(hmgets(map3, s.key).d == i*5);
- //STBDS_ASSERT(hmgetp(map3, t.key) == 0);
- }
-#endif
-}
-#endif
-
-
-/*
-------------------------------------------------------------------------------
-This software is available under 2 licenses -- choose whichever you prefer.
-------------------------------------------------------------------------------
-ALTERNATIVE A - MIT License
-Copyright (c) 2019 Sean Barrett
-Permission is hereby granted, free of charge, to any person obtaining a copy of
-this software and associated documentation files (the "Software"), to deal in
-the Software without restriction, including without limitation the rights to
-use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies
-of the Software, and to permit persons to whom the Software is furnished to do
-so, subject to the following conditions:
-The above copyright notice and this permission notice shall be included in all
-copies or substantial portions of the Software.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER
-LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM,
-OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE
-SOFTWARE.
-------------------------------------------------------------------------------
-ALTERNATIVE B - Public Domain (www.unlicense.org)
-This is free and unencumbered software released into the public domain.
-Anyone is free to copy, modify, publish, use, compile, sell, or distribute this
-software, either in source code form or as a compiled binary, for any purpose,
-commercial or non-commercial, and by any means.
-In jurisdictions that recognize copyright laws, the author or authors of this
-software dedicate any and all copyright interest in the software to the public
-domain. We make this dedication for the benefit of the public at large and to
-the detriment of our heirs and successors. We intend this dedication to be an
-overt act of relinquishment in perpetuity of all present and future rights to
-this software under copyright law.
-THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR
-IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY,
-FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE
-AUTHORS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN
-ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION
-WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.
-------------------------------------------------------------------------------
-*/
diff --git a/src/main.c b/src/main.c
deleted file mode 100644
index bb4d2a00..00000000
--- a/src/main.c
+++ /dev/null
@@ -1,65 +0,0 @@
-
-// #include "mainwindow.h"
-#include "containers_view.h"
-#include "contents_view.h"
-#include "attributes_view.h"
-#include "menu_bar.h"
-#include "entry.h"
-#include "utils.h"
-
-#include "active_directory.h"
-#include
-
-int main(int argc, char** argv) {
- // TODO: show some kind of progress indicator before loading data? it's not instant
-
- bool fake_entries = false;
- for (int i = 0; i < argc; i++) {
- if (strcmp(argv[i], "fake") == 0) {
- fake_entries = true;
- }
- }
-
- if (fake_entries) {
- entry_init_fake();
- } else {
- // Login into ldap
- LDAP* ldap_connection = ad_login();
- if (ldap_connection == NULL) {
- printf("ad_login error: %s\n", ad_get_error());
- return 0;
- }
-
- // Load entry data
- entry_init();
- }
-
-
- // Setup UI
- gtk_init(&argc, &argv);
-
- // Load builder
- GtkBuilder *builder = gtk_builder_new_from_file("data/adtool.glade");
- if (builder == NULL) {
- printf("Failed to load glade file\n");
-
- return 0;
- }
-
- // NOTE: inits have to be in this order
- attributes_init(builder);
- contents_init(builder);
- containers_init(builder);
- menu_bar_init(builder);
-
- gtk_builder_connect_signals(builder, NULL);
-
- GtkWidget* window = GTK_WIDGET(gtk_builder_get_object_CHECKED(builder, "window"));
- gtk_widget_show(window);
-
- g_object_unref(G_OBJECT(builder));
-
- gtk_main();
-
- return 0;
-}
diff --git a/src/main.cpp b/src/main.cpp
new file mode 100644
index 00000000..f4ac015f
--- /dev/null
+++ b/src/main.cpp
@@ -0,0 +1,81 @@
+
+#include "containers_view.h"
+#include "contents_view.h"
+#include "attributes_view.h"
+#include "ad_filter.h"
+#include "ad_model.h"
+#include "attributes_model.h"
+#include "ui_mainwindow.h"
+#include "ad_interface.h"
+
+#include
+#include
+
+int main(int argc, char **argv) {
+ // Load fake AD data if given "fake" argument
+ // This also swaps all ad interface functions to their fake versions (including login)
+ if (argc >= 1 && QString(argv[0]) == "fake") {
+ FAKE_AD = true;
+ }
+
+ if (!ad_interface_login()) {
+ return 1;
+ }
+
+ QApplication app(argc, argv);
+
+ //
+ // Setup ui
+ //
+ Ui::MainWindow ui;
+ QMainWindow main_window;
+ ui.setupUi(&main_window);
+
+ AdModel ad_model;
+
+ // Attributes
+ {
+ auto attributes_model = new AttributesModel();
+ ui.attributes_view->setModel(attributes_model);
+ }
+
+ // Contents
+ {
+ auto view = ui.contents_view;
+
+ auto proxy = new AdFilter(ui.menubar_view_advancedView);
+ proxy->setSourceModel(&ad_model);
+ view->setModel(proxy);
+ }
+
+ // Containers
+ {
+ auto view = ui.containers_view;
+
+ auto proxy = new AdFilter(ui.menubar_view_advancedView, true);
+ proxy->setSourceModel(&ad_model);
+ view->setModel(proxy);
+
+ // NOTE: have to hide columns after setting model
+ view->hideColumn(AdModel::Column::Category);
+ view->hideColumn(AdModel::Column::Description);
+ }
+
+ //
+ // Connect signals
+ //
+
+ // Set root index of contents view to selection of containers view
+ QObject::connect(
+ ui.containers_view->selectionModel(), &QItemSelectionModel::selectionChanged,
+ ui.contents_view, &ContentsView::set_root_index_from_selection);
+
+ // Set target of attributes view to selection of contents view
+ QObject::connect(
+ ui.contents_view->selectionModel(), &QItemSelectionModel::selectionChanged,
+ ui.attributes_view, &AttributesView::set_target_from_selection);
+
+ main_window.show();
+
+ return app.exec();
+}
diff --git a/src/menu_bar.c b/src/menu_bar.c
deleted file mode 100644
index 18c6d960..00000000
--- a/src/menu_bar.c
+++ /dev/null
@@ -1,46 +0,0 @@
-
-#include "menu_bar.h"
-#include "containers_view.h"
-#include "contents_view.h"
-#include "entry.h"
-#include "utils.h"
-
-bool advanced_view = false;
-
-GtkDialog* new_user_dialog = NULL;
-GtkEntry* new_user_name_entry = NULL;
-
-bool advanced_view_is_on() {
- return advanced_view;
-}
-
-void advanced_view_toggled_func(GtkWidget *widget, gpointer data) {
- advanced_view = gtk_check_menu_item_get_active(GTK_CHECK_MENU_ITEM(widget));
-
- containers_refilter();
- contents_refilter();
-}
-
-void menu_bar_file_new_user_func(GtkWidget *widget, gpointer data) {
- // TODO: set max chars for entry (in glade) based on ldap limits
- int response = gtk_dialog_run(new_user_dialog);
-
- if (response == GTK_RESPONSE_OK) {
- printf("new user is %s\n", gtk_entry_get_text(new_user_name_entry));
-
- entry_new(gtk_entry_get_text(new_user_name_entry), NewEntryType_User);
-
- gtk_entry_set_text(new_user_name_entry, "");
- gtk_widget_hide(GTK_WIDGET(new_user_dialog));
- } else if (response == GTK_RESPONSE_CANCEL) {
- gtk_entry_set_text(new_user_name_entry, "");
- gtk_widget_hide(GTK_WIDGET(new_user_dialog));
- } else {
- printf("ERROR: menu_bar_file_new_user_func() received unknown button response %d !\n", response);
- }
-}
-
-void menu_bar_init(GtkBuilder* builder) {
- new_user_dialog = GTK_DIALOG(gtk_builder_get_object_CHECKED(builder, "new_user_dialog"));
- new_user_name_entry = GTK_ENTRY(gtk_builder_get_object_CHECKED(builder, "new_user_dialog_name_entry"));
-}
\ No newline at end of file
diff --git a/src/menu_bar.h b/src/menu_bar.h
deleted file mode 100644
index e3aec038..00000000
--- a/src/menu_bar.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#include
-#include
-
-bool advanced_view_is_on();
-void menu_bar_init(GtkBuilder* builder);
diff --git a/src/ui/ui_mainwindow.h b/src/ui/ui_mainwindow.h
new file mode 100644
index 00000000..f71cdfb3
--- /dev/null
+++ b/src/ui/ui_mainwindow.h
@@ -0,0 +1,142 @@
+/********************************************************************************
+** Form generated from reading UI file 'mainwindow.ui'
+**
+** Created by: Qt User Interface Compiler version 5.12.8
+**
+** WARNING! All changes made in this file will be lost when recompiling UI file!
+********************************************************************************/
+
+#ifndef UI_MAINWINDOW_H
+#define UI_MAINWINDOW_H
+
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include
+#include "src/attributes_view.h"
+#include "src/containers_view.h"
+#include "src/contents_view.h"
+
+QT_BEGIN_NAMESPACE
+
+class Ui_MainWindow
+{
+public:
+ QAction *actionWhat;
+ QAction *actionSecond;
+ QAction *actionSomething;
+ QAction *actionHere;
+ QAction *menubar_view_advancedView;
+ QWidget *centralwidget;
+ QSplitter *splitter;
+ ContainersView *containers_view;
+ ContentsView *contents_view;
+ AttributesView *attributes_view;
+ QMenuBar *menubar;
+ QMenu *menuhello;
+ QMenu *menuEdit;
+ QMenu *menuView;
+ QStatusBar *statusbar;
+
+ void setupUi(QMainWindow *MainWindow)
+ {
+ if (MainWindow->objectName().isEmpty())
+ MainWindow->setObjectName(QString::fromUtf8("MainWindow"));
+ MainWindow->resize(1307, 637);
+ actionWhat = new QAction(MainWindow);
+ actionWhat->setObjectName(QString::fromUtf8("actionWhat"));
+ actionSecond = new QAction(MainWindow);
+ actionSecond->setObjectName(QString::fromUtf8("actionSecond"));
+ actionSomething = new QAction(MainWindow);
+ actionSomething->setObjectName(QString::fromUtf8("actionSomething"));
+ actionHere = new QAction(MainWindow);
+ actionHere->setObjectName(QString::fromUtf8("actionHere"));
+ menubar_view_advancedView = new QAction(MainWindow);
+ menubar_view_advancedView->setObjectName(QString::fromUtf8("menubar_view_advancedView"));
+ menubar_view_advancedView->setCheckable(true);
+ centralwidget = new QWidget(MainWindow);
+ centralwidget->setObjectName(QString::fromUtf8("centralwidget"));
+ splitter = new QSplitter(centralwidget);
+ splitter->setObjectName(QString::fromUtf8("splitter"));
+ splitter->setGeometry(QRect(0, 0, 1301, 591));
+ splitter->setOrientation(Qt::Horizontal);
+ containers_view = new ContainersView(splitter);
+ containers_view->setObjectName(QString::fromUtf8("containers_view"));
+ containers_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ containers_view->setRootIsDecorated(true);
+ containers_view->setItemsExpandable(true);
+ containers_view->setExpandsOnDoubleClick(true);
+ splitter->addWidget(containers_view);
+ containers_view->header()->setVisible(true);
+ contents_view = new ContentsView(splitter);
+ contents_view->setObjectName(QString::fromUtf8("contents_view"));
+ contents_view->setEditTriggers(QAbstractItemView::NoEditTriggers);
+ contents_view->setSelectionMode(QAbstractItemView::SingleSelection);
+ contents_view->setRootIsDecorated(false);
+ contents_view->setItemsExpandable(false);
+ contents_view->setExpandsOnDoubleClick(false);
+ splitter->addWidget(contents_view);
+ contents_view->header()->setVisible(true);
+ attributes_view = new AttributesView(splitter);
+ attributes_view->setObjectName(QString::fromUtf8("attributes_view"));
+ attributes_view->setEditTriggers(QAbstractItemView::DoubleClicked|QAbstractItemView::EditKeyPressed);
+ attributes_view->setSelectionMode(QAbstractItemView::NoSelection);
+ attributes_view->setSelectionBehavior(QAbstractItemView::SelectRows);
+ splitter->addWidget(attributes_view);
+ MainWindow->setCentralWidget(centralwidget);
+ menubar = new QMenuBar(MainWindow);
+ menubar->setObjectName(QString::fromUtf8("menubar"));
+ menubar->setGeometry(QRect(0, 0, 1307, 27));
+ menuhello = new QMenu(menubar);
+ menuhello->setObjectName(QString::fromUtf8("menuhello"));
+ menuEdit = new QMenu(menubar);
+ menuEdit->setObjectName(QString::fromUtf8("menuEdit"));
+ menuView = new QMenu(menubar);
+ menuView->setObjectName(QString::fromUtf8("menuView"));
+ MainWindow->setMenuBar(menubar);
+ statusbar = new QStatusBar(MainWindow);
+ statusbar->setObjectName(QString::fromUtf8("statusbar"));
+ MainWindow->setStatusBar(statusbar);
+
+ menubar->addAction(menuhello->menuAction());
+ menubar->addAction(menuEdit->menuAction());
+ menubar->addAction(menuView->menuAction());
+ menuhello->addAction(actionWhat);
+ menuhello->addAction(actionSecond);
+ menuEdit->addAction(actionSomething);
+ menuEdit->addAction(actionHere);
+ menuView->addAction(menubar_view_advancedView);
+
+ retranslateUi(MainWindow);
+
+ QMetaObject::connectSlotsByName(MainWindow);
+ } // setupUi
+
+ void retranslateUi(QMainWindow *MainWindow)
+ {
+ MainWindow->setWindowTitle(QApplication::translate("MainWindow", "MainWindow", nullptr));
+ actionWhat->setText(QApplication::translate("MainWindow", "What", nullptr));
+ actionSecond->setText(QApplication::translate("MainWindow", "Second", nullptr));
+ actionSomething->setText(QApplication::translate("MainWindow", "Something", nullptr));
+ actionHere->setText(QApplication::translate("MainWindow", "Here", nullptr));
+ menubar_view_advancedView->setText(QApplication::translate("MainWindow", "Advanced view", nullptr));
+ menuhello->setTitle(QApplication::translate("MainWindow", "New", nullptr));
+ menuEdit->setTitle(QApplication::translate("MainWindow", "Edit", nullptr));
+ menuView->setTitle(QApplication::translate("MainWindow", "View", nullptr));
+ } // retranslateUi
+
+};
+
+namespace Ui {
+ class MainWindow: public Ui_MainWindow {};
+} // namespace Ui
+
+QT_END_NAMESPACE
+
+#endif // UI_MAINWINDOW_H
diff --git a/src/utils.c b/src/utils.c
deleted file mode 100644
index b54ddb90..00000000
--- a/src/utils.c
+++ /dev/null
@@ -1,22 +0,0 @@
-
-#include "utils.h"
-
-#include
-#include
-
-bool streql(const char* a, const char* b) {
- return strcmp(a, b) == 0;
-}
-
-// Use this instead of regular gtk_builder_get_object() so that
-// on error the object's name is printed
-GObject* gtk_builder_get_object_CHECKED(GtkBuilder* builder, const char *name) {
- GObject* object = gtk_builder_get_object(builder, name);
-
- if (object != NULL) {
- return object;
- } else {
- printf("ERROR: gtk_builder_get_object() failed to get \"%s\"\n", name);
- return NULL;
- }
-}
diff --git a/src/utils.h b/src/utils.h
deleted file mode 100644
index 35cf24b2..00000000
--- a/src/utils.h
+++ /dev/null
@@ -1,7 +0,0 @@
-#pragma once
-
-#include
-#include
-
-bool streql(const char* a, const char* b);
-GObject* gtk_builder_get_object_CHECKED(GtkBuilder* builder, const char *name);
\ No newline at end of file