diff --git a/data/me.ppvan.psequel.gschema.xml b/data/me.ppvan.psequel.gschema.xml
index ab32c73..8e90cda 100644
--- a/data/me.ppvan.psequel.gschema.xml
+++ b/data/me.ppvan.psequel.gschema.xml
@@ -26,5 +26,13 @@
+
+ 200
+ Select query limit
+
+ The limit of select query in the query, get 500 rows max for each query
+
+
+
diff --git a/res/gtk/icons/left-large-symbolic.svg b/res/gtk/icons/left-large-symbolic.svg
new file mode 100644
index 0000000..7061d6d
--- /dev/null
+++ b/res/gtk/icons/left-large-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/res/gtk/icons/right-large-symbolic.svg b/res/gtk/icons/right-large-symbolic.svg
new file mode 100644
index 0000000..c8d83ea
--- /dev/null
+++ b/res/gtk/icons/right-large-symbolic.svg
@@ -0,0 +1,2 @@
+
+
diff --git a/res/gtk/query-view.blp b/res/gtk/query-view.blp
index 82b461b..abb7943 100644
--- a/res/gtk/query-view.blp
+++ b/res/gtk/query-view.blp
@@ -37,8 +37,7 @@ template $PsequelQueryView : Adw.Bin {
}
-
- Separator {}
+
Adw.ViewSwitcher tables_views_switcher {
@@ -46,12 +45,14 @@ template $PsequelQueryView : Adw.Bin {
height-request: 40;
margin-start: 8;
margin-end: 8;
- margin-top: 8;
- // margin-bottom: 8;
+ margin-top: 4;
+ margin-bottom: 8;
policy: wide;
stack: sql_views;
}
+ Separator {}
+
Adw.ViewStack sql_views {
hexpand: true;
vexpand: true;
@@ -108,6 +109,8 @@ template $PsequelQueryView : Adw.Bin {
ListBox table_list {
styles ["navigation-sidebar"]
selected-rows-changed => $table_selected();
+ row-activated => $table_activated();
+ activate-on-single-click: false;
}
}
@@ -141,7 +144,7 @@ template $PsequelQueryView : Adw.Bin {
ToggleButton search_views_btn {
styles ["flat"]
- tooltip-text: "Search tables";
+ tooltip-text: "Search views";
icon-name: "loupe-large-symbolic";
toggled => $on_show_view_search();
@@ -167,6 +170,7 @@ template $PsequelQueryView : Adw.Bin {
ListBox views_list {
styles ["navigation-sidebar"]
selected-rows-changed => $view_selected();
+ row-activated => $view_activated();
}
}
};
@@ -175,18 +179,13 @@ template $PsequelQueryView : Adw.Bin {
Box {
spacing: 4;
- margin-bottom: 4;
+ margin-bottom: 8;
margin-end: 8;
- Button logout {
+ Button {
styles ["flat"]
- icon-name: "step-out-symbolic";
- tooltip-text: "Logout";
- clicked => $on_logout_clicked();
- }
-
- Label {
- hexpand: true;
+ icon-name: "plus-large-symbolic";
+ tooltip-text: "Create new table";
}
Button reload {
@@ -197,12 +196,17 @@ template $PsequelQueryView : Adw.Bin {
clicked => $on_reload_clicked();
}
- Button {
+ Label {
+ hexpand: true;
+ }
+
+ Button logout {
styles ["flat"]
- icon-name: "plus-large-symbolic";
- tooltip-text: "Create new table";
+ icon-name: "step-out-symbolic";
+ tooltip-text: "Logout";
+ clicked => $on_logout_clicked();
}
-
+
}
}
@@ -232,11 +236,11 @@ template $PsequelQueryView : Adw.Bin {
}
Adw.ViewStackPage {
- name: "Data";
+ name: "data-view";
title: "Data";
icon-name: "object-rows-symbolic";
- child: Label {
- label: "Data";
+ child: $PsequelTableData {
+
};
}
diff --git a/res/gtk/table-data.blp b/res/gtk/table-data.blp
index e69de29..18be54f 100644
--- a/res/gtk/table-data.blp
+++ b/res/gtk/table-data.blp
@@ -0,0 +1,85 @@
+using Gtk 4.0;
+
+using Adw 1;
+
+template $PsequelTableData : Gtk.Box {
+
+ width-request: 900;
+ height-request: 600;
+
+ orientation: vertical;
+ spacing: 4;
+ margin-start: 8;
+ margin-top: 8;
+ margin-end: 8;
+ margin-bottom: 8;
+
+ Box {
+ spacing: 12;
+ margin-top: 8;
+ margin-bottom: 8;
+ Entry filter_entry {
+ hexpand: true;
+ placeholder-text: "WHERE clause";
+ activate => $on_entry_activated();
+ }
+
+ Button filter_btn {
+ styles ["suggested-action"]
+ label: "Filter";
+
+ clicked => $filter_query();
+ }
+ }
+
+ ScrolledWindow {
+ ColumnView data_view {
+ show-row-separators: true;
+ show-column-separators: true;
+ styles ["data-table"]
+ vexpand: true;
+ }
+ }
+
+
+ Box {
+ spacing: 8;
+ // height-request: 40;
+ Button {
+ styles ["flat"]
+ icon-name: "plus-large-symbolic";
+ tooltip-text: "Insert Row";
+ }
+
+ Button reload {
+ styles ["flat"]
+ icon-name: "refresh-large-symbolic";
+ tooltip-text: "Reload Data";
+ clicked => $reload_data();
+ }
+
+ Separator {}
+
+ Label status_label {
+ label: "Rows 1 - 500";
+ hexpand: true;
+ halign: start;
+ }
+
+ Separator {}
+
+ Button left_page {
+ styles ["flat"]
+ icon-name: "left-large-symbolic";
+ tooltip-text: "Last Page";
+ clicked => $load_previous_page();
+ }
+
+ Button right_page {
+ styles ["flat"]
+ icon-name: "right-large-symbolic";
+ tooltip-text: "Next Page";
+ clicked => $load_next_page();
+ }
+ }
+}
\ No newline at end of file
diff --git a/res/gtk/table-structure.blp b/res/gtk/table-structure.blp
index 896f8d0..e507b24 100644
--- a/res/gtk/table-structure.blp
+++ b/res/gtk/table-structure.blp
@@ -20,6 +20,8 @@ template $PsequelTableStructure : Gtk.Box {
}
ScrolledWindow {
ColumnView columns {
+
+ styles ["data-table"]
show-column-separators: true;
show-row-separators: true;
vexpand: true;
@@ -35,6 +37,7 @@ template $PsequelTableStructure : Gtk.Box {
ScrolledWindow {
ColumnView indexes {
+ styles ["data-table"]
show-column-separators: true;
show-row-separators: true;
vexpand: true;
@@ -50,6 +53,7 @@ template $PsequelTableStructure : Gtk.Box {
ScrolledWindow {
ColumnView foreign_key {
+ styles ["data-table"]
show-column-separators: true;
show-row-separators: true;
vexpand: true;
diff --git a/res/meson.build b/res/meson.build
index 69d0116..044847d 100644
--- a/res/meson.build
+++ b/res/meson.build
@@ -12,6 +12,7 @@ blueprints = custom_target('blueprints',
'gtk/connection-form.blp',
'gtk/query-view.blp',
'gtk/table-structure.blp',
+ 'gtk/table-data.blp',
),
build_by_default: true,
# This does not run if you use an dot (.)
diff --git a/res/psequel.gresource.xml b/res/psequel.gresource.xml
index 55ba3bd..907438a 100644
--- a/res/psequel.gresource.xml
+++ b/res/psequel.gresource.xml
@@ -5,6 +5,7 @@
gtk/welcome.ui
gtk/query-view.ui
gtk/table-structure.ui
+ gtk/table-data.ui
gtk/recent-connection.ui
gtk/connection-form.ui
gtk/window.ui
@@ -23,5 +24,7 @@
gtk/icons/step-out-symbolic.svg
gtk/icons/arrow-into-box-symbolic.svg
gtk/icons/category-search-symbolic.svg
+ gtk/icons/left-large-symbolic.svg
+ gtk/icons/right-large-symbolic.svg
diff --git a/src/application.vala b/src/application.vala
index 2d12dde..722aca2 100644
--- a/src/application.vala
+++ b/src/application.vala
@@ -51,7 +51,7 @@ namespace Psequel {
try {
background = new ThreadPool.with_owned_data ((worker) => {
- worker.task ();
+ worker.run ();
}, ResourceManager.POOL_SIZE, false);
} catch (ThreadError err) {
debug (err.message);
@@ -94,6 +94,7 @@ namespace Psequel {
typeof (Psequel.ConnectionForm).ensure ();
typeof (Psequel.QueryView).ensure ();
typeof (Psequel.TableStructure).ensure ();
+ typeof (Psequel.TableData).ensure ();
}
}
}
\ No newline at end of file
diff --git a/src/meson.build b/src/meson.build
index 381c916..03da880 100644
--- a/src/meson.build
+++ b/src/meson.build
@@ -7,6 +7,7 @@ psequel_sources = [
'ui/window.vala',
'ui/query_view.vala',
'ui/table_structure.vala',
+ 'ui/table_data.vala',
'models/connection.vala',
'models/schema.vala',
'models/table.vala',
diff --git a/src/models/table.vala b/src/models/table.vala
index 654476e..be6fb81 100644
--- a/src/models/table.vala
+++ b/src/models/table.vala
@@ -12,6 +12,7 @@ namespace Psequel {
private ArrayList data;
private ArrayList headers;
+ private ArrayList cols_type;
public Relation (owned Result res) {
Object ();
@@ -23,6 +24,16 @@ namespace Psequel {
this.data = data;
this.rows = data.size;
this.cols = headers.size;
+
+ this.cols_type = new ArrayList ();
+ // Fix me in the future
+ for (int i = 0; i < headers.size; i++) {
+ this.cols_type.add (Type.STRING);
+ }
+ }
+
+ public Type get_column_type (int index) {
+ return this.cols_type[index];
}
private void load_data (owned Result result) {
@@ -32,7 +43,43 @@ namespace Psequel {
cols = result.get_n_fields ();
this.headers = new ArrayList ();
+ this.cols_type = new ArrayList ();
for (int i = 0; i < cols; i++) {
+
+ // Oid, should have enum for value type in VAPI but no.
+ switch ((uint)result.get_field_type (i)) {
+ case 20, 21, 23:
+ // int
+ this.cols_type.add (Type.INT64);
+ break;
+ case 16:
+ // bool
+ this.cols_type.add (Type.BOOLEAN);
+ break;
+ case 700, 701:
+ // real
+ this.cols_type.add (Type.DOUBLE);
+ break;
+ case 25, 1043, 18, 19, 1700:
+ // string
+ this.cols_type.add (Type.STRING);
+ break;
+ case 1114:
+ // timestamp
+ this.cols_type.add (Type.STRING);
+ break;
+ case 1082:
+ // date
+ this.cols_type.add (Type.STRING);
+ break;
+
+ default:
+ debug ("Programming errors, unhandled Oid: %u", (uint)result.get_field_type (i));
+ this.cols_type.add (Type.STRING);
+ break;
+ // assert_not_reached ();
+ }
+
headers.add (result.get_field_name (i));
}
@@ -40,6 +87,7 @@ namespace Psequel {
for (int i = 0; i < rows; i++) {
data.add (new Row ());
+
for (int j = 0; j < cols; j++) {
data[i].add_field (result.get_value (i, j));
}
@@ -58,6 +106,14 @@ namespace Psequel {
return new Relation.from_data (new_headers, new_rows);
}
+ public string get_header (int index) {
+ if (index >= cols) {
+ return "";
+ }
+
+ return headers.get (index);
+ }
+
public string to_string () {
return @"Table ($rows x $cols)";
}
@@ -104,7 +160,10 @@ namespace Psequel {
data.remove_at (index);
}
- public new string @get (int index) {
+ public new string? @get (int index) {
+ if (index >= size) {
+ return null;
+ }
return data.get (index);
}
diff --git a/src/models/utils.vala b/src/models/utils.vala
index 6ab2196..7650b1b 100644
--- a/src/models/utils.vala
+++ b/src/models/utils.vala
@@ -4,6 +4,37 @@ namespace Psequel {
public delegate void Job ();
+ public Adw.MessageDialog create_dialog (string heading, string body) {
+ var window = ResourceManager.instance ().app.active_window;
+ var dialog = new Adw.MessageDialog (window, heading, body);
+
+ dialog.close_response = "okay";
+ dialog.add_response ("okay", "OK");
+
+ return dialog;
+ }
+
+ /* Model specialize for storeing query result. */
+ public class BinddingArray: ListModel, Object {
+ private Array _data;
+
+ public BinddingArray (int capacity) {
+ _data = new Array.sized (true, false, sizeof (string), capacity);
+ }
+
+ public GLib.Object? get_item (uint position) {
+ return (GLib.Object)_data.index (position);
+ }
+
+ public GLib.Type get_item_type () {
+ return Type.STRING;
+ }
+
+ public uint get_n_items () {
+ return _data.length;
+ }
+ }
+
public class ObservableArrayList: ListModel, Object {
private ArrayList _data;
@@ -64,6 +95,19 @@ namespace Psequel {
this.thread_name = name;
this.task = (owned)task;
}
+
+ public void run () {
+
+ // Thread.usleep ((ulong)1e6);
+ this.task ();
+ }
+ }
+
+ namespace Views {
+ public const string CONNECTION = "connection-view";
+ public const string QUERY = "query-view";
+ public const string TABLE_STRUCTURE = "structure-view";
+ public const string TABLE_DATA = "data-view";
}
public errordomain PsequelError {
diff --git a/src/services/query_service.vala b/src/services/query_service.vala
index febefc8..9b8f9e2 100644
--- a/src/services/query_service.vala
+++ b/src/services/query_service.vala
@@ -16,13 +16,29 @@ namespace Psequel {
}
public async Relation db_schemas () throws PsequelError {
- var stmt = "select schema_name from information_schema.schemata;";
+ var stmt = """
+ SELECT schema_name FROM information_schema.schemata
+ WHERE schema_name NOT IN ('pg_catalog', 'information_schema');
+ """;
var res = yield exec_query (stmt);
return res;
}
+ public async Relation select (string schema, string table_name, int offset = 0, int limit = 500, string where_clause = "") throws PsequelError {
+
+ if (where_clause == "") {
+ string stmt = @"SELECT * FROM $schema.$table_name LIMIT $limit OFFSET $offset";
+ return yield exec_query (stmt);
+ } else {
+ string stmt = @"SELECT * FROM $schema.$table_name WHERE $where_clause LIMIT $limit OFFSET $offset";
+ return yield exec_query (stmt);
+ }
+
+
+ }
+
public async Relation db_table_fk (string schema, string table_name) throws PsequelError {
string stmt = """
SELECT conname, pg_catalog.pg_get_constraintdef(r.oid, true) as condef
@@ -64,7 +80,6 @@ namespace Psequel {
debug ("Regex not match: %s", fk_def);
assert_not_reached ();
}
- debug ("%s", new_row.to_string ());
return new_row;
});
diff --git a/src/services/resource_manager.vala b/src/services/resource_manager.vala
index 1d2086c..a811c13 100644
--- a/src/services/resource_manager.vala
+++ b/src/services/resource_manager.vala
@@ -21,6 +21,7 @@ namespace Psequel {
// Should not be > 1 because libpq can't create many query in 1 connection.
public const int POOL_SIZE = 1;
+ public const int MAX_COLUMNS = 100;
public ThreadPool background;
/**
@@ -82,6 +83,7 @@ namespace Psequel {
});
} catch (Error err) {
debug (err.message);
+ recent_connections.clear ();
}
debug ("User setting loaded");
diff --git a/src/services/signal.vala b/src/services/signal.vala
index f6f7a92..c8517bd 100644
--- a/src/services/signal.vala
+++ b/src/services/signal.vala
@@ -13,6 +13,8 @@ namespace Psequel {
public signal void table_selected_changed (string schema, string table);
+ public signal void table_activated (string schema, string table);
+ public signal void view_activated (string schema, string view);
public signal void database_connected ();
/**
diff --git a/src/ui/connection_form.vala b/src/ui/connection_form.vala
index 89d279d..530691b 100644
--- a/src/ui/connection_form.vala
+++ b/src/ui/connection_form.vala
@@ -77,7 +77,7 @@ namespace Psequel {
signals.database_connected ();
var window = (Window) ResourceManager.instance ().app.get_active_window ();
- window.navigate_to ("query-view");
+ window.navigate_to (Views.QUERY);
} catch (PsequelError err) {
var dialog = create_err_dialog ("Connection error", err.message);
dialog.present ();
diff --git a/src/ui/query_view.vala b/src/ui/query_view.vala
index eea0a7e..aba89ff 100644
--- a/src/ui/query_view.vala
+++ b/src/ui/query_view.vala
@@ -85,7 +85,6 @@ namespace Psequel {
signals.views_list_changed ();
}
-
[GtkCallback]
private void table_selected () {
var row = table_list.get_selected_row ();
@@ -96,12 +95,37 @@ namespace Psequel {
var cur_schema = schema.get_selected_item () as Gtk.StringObject;
assert_nonnull (cur_schema);
- var label = row.child.get_last_child () as Gtk.Label;
+ var tbname = table_names.get_item (row.get_index ()) as Relation.Row;
debug ("Emit table_selected_changed");
- signals.table_selected_changed (cur_schema.string, label.get_label ());
+ signals.table_selected_changed (cur_schema.string, tbname[0]);
}
}
+
+ [GtkCallback]
+ private void table_activated (Gtk.ListBoxRow row) {
+
+ var cur_schema = schema.get_selected_item () as Gtk.StringObject;
+ assert_nonnull (cur_schema);
+
+
+ var tbname = table_names.get_item (row.get_index ()) as Relation.Row;
+ debug ("Emit table_activated");
+ signals.table_activated (cur_schema.string, tbname[0]);
+ }
+
+ [GtkCallback]
+ private void view_activated (Gtk.ListBoxRow row) {
+
+ var cur_schema = schema.get_selected_item () as Gtk.StringObject;
+ assert_nonnull (cur_schema);
+
+
+ var vname = views_names.get_item (row.get_index ()) as Relation.Row;
+ debug ("Emit view_activated");
+ signals.view_activated (cur_schema.string, vname[0]);
+ }
+
/**
* Filter table name base on seach entry.
*/
@@ -118,7 +142,7 @@ namespace Psequel {
/**
* Filter table name base on seach entry.
*/
- private bool view_filter_func (Object item) {
+ private bool view_filter_func (Object item) {
assert (item is Relation.Row);
var row = item as Relation.Row;
@@ -146,7 +170,7 @@ namespace Psequel {
schema_model.append (item[0]);
}
- debug ("Schema loaded.");
+ debug ("Schema reloaded.");
}
/**
@@ -179,7 +203,7 @@ namespace Psequel {
views_names.add (item);
}
- debug ("Views list reloaded, got %d views in schema %s", table_names.size, cur_schema);
+ debug ("Views list reloaded, got %d views in schema %s", views_names.size, cur_schema);
}
/** Create row widget from query result.
@@ -198,6 +222,7 @@ namespace Psequel {
box.append (label);
row.child = box;
+ row.tooltip_text = "Double click to load data";
return row;
}
@@ -257,6 +282,18 @@ namespace Psequel {
reload_schema.begin ();
});
+ signals.table_activated.connect (() => {
+ debug ("handle table_activated");
+
+ stack.set_visible_child_name (Views.TABLE_DATA);
+ });
+
+ signals.view_activated.connect (() => {
+ debug ("handle table_activated");
+
+ stack.set_visible_child_name (Views.TABLE_DATA);
+ });
+
schema.notify["selected"].connect (schema_changed);
}
@@ -274,5 +311,8 @@ namespace Psequel {
[GtkChild]
private unowned Gtk.DropDown schema;
+
+ [GtkChild]
+ private unowned Adw.ViewStack stack;
}
}
\ No newline at end of file
diff --git a/src/ui/table_data.vala b/src/ui/table_data.vala
new file mode 100644
index 0000000..b364be7
--- /dev/null
+++ b/src/ui/table_data.vala
@@ -0,0 +1,217 @@
+using Gee;
+namespace Psequel {
+
+ [GtkTemplate (ui = "/me/ppvan/psequel/gtk/table-data.ui")]
+ public class TableData : Gtk.Box {
+
+ private AppSignals signals;
+ private QueryService query_service;
+
+ private ObservableArrayList model;
+
+ private ArrayList backup;
+
+ private Gtk.SortListModel sort_model;
+
+
+ public int query_limit { get; set; }
+
+ public string schema {get; private set; default = "public";}
+ public string tbname {get; private set;}
+ public int current_page { get; private set; default = 0; }
+
+ public TableData () {
+ Object ();
+ }
+
+ construct {
+ query_service = ResourceManager.instance ().query_service;
+ signals = ResourceManager.instance ().signals;
+
+ var setting = ResourceManager.instance ().settings;
+ setting.bind ("query-limit", this, "query-limit", SettingsBindFlags.DEFAULT);
+
+ this.bind_property ("current_page", status_label, "label", BindingFlags.SYNC_CREATE, (bindding, from, ref to) => {
+ int curr_page = from.get_int ();
+ to.set_string (@"Rows $(curr_page * query_limit + 1) - $(query_limit * (curr_page + 1))");
+
+ return true;
+ });
+
+ this.bind_property ("current_page", left_page, "sensitive", BindingFlags.SYNC_CREATE, (bindding, from, ref to) => {
+ int curr_page = from.get_int ();
+ to.set_boolean (curr_page > 0);
+
+ return true;
+ });
+
+ model = new ObservableArrayList ();
+ backup = new ArrayList ();
+
+ signals.table_activated.connect_after ((schema, tbname) => {
+ this.schema = schema;
+ this.tbname = tbname;
+ this.filter_entry.set_text ("");
+ load_data.begin (schema, tbname);
+ });
+
+ signals.view_activated.connect_after ((schema, vname) => {
+ this.schema = schema;
+ this.tbname = tbname;
+ this.filter_entry.set_text ("");
+ load_data.begin (schema, vname);
+ });
+
+
+ for (int i = 0; i < ResourceManager.MAX_COLUMNS; i++) {
+ var factory = new Gtk.SignalListItemFactory ();
+ factory.set_data ("index", i);
+
+ factory.setup.connect ((_fact, _item) => {
+ var label = new Gtk.Label (null);
+ label.halign = Gtk.Align.START;
+ label.margin_start = 8;
+ _item.child = label;
+ });
+
+ factory.bind.connect ((_fact, _item) => {
+ var row = _item.item as Relation.Row;
+ var label = _item.child as Gtk.Label;
+ int index = _fact.get_data ("index");
+ label.label = row[index];
+ });
+
+ Gtk.ColumnViewColumn column = new Gtk.ColumnViewColumn ("", factory);
+ column.set_expand (true);
+ column.set_visible (false);
+
+ data_view.append_column (column);
+
+ this.sort_model = new Gtk.SortListModel (model, null);
+
+ // assert_nonnull (model);
+
+ var selection_model = new Gtk.SingleSelection (sort_model);
+ data_view.set_model (selection_model);
+ }
+ }
+
+ [GtkCallback]
+ private void filter_query (Gtk.Button btn) {
+ var where_clause = filter_entry.get_text ();
+ load_data.begin (schema, tbname, current_page, where_clause);
+ }
+
+ [GtkCallback]
+ private void on_entry_activated (Gtk.Entry entry) {
+ filter_btn.clicked ();
+ }
+
+
+ [GtkCallback]
+ private void load_next_page (Gtk.Button btn) {
+ load_data.begin (schema, tbname, ++current_page);
+
+
+ }
+
+ [GtkCallback]
+ private void load_previous_page (Gtk.Button btn) {
+ load_data.begin (schema, tbname, --current_page);
+ }
+
+ [GtkCallback]
+ private void reload_data (Gtk.Button btn) {
+ load_data.begin (schema, tbname, current_page);
+ }
+
+ public void table_double_clicked () {
+ debug ("Activated");
+ }
+
+ public async void load_data (string schema, string table_name, int page = 0, string where = "") {
+
+ try {
+
+ Relation relation = yield query_service.select (schema, table_name, page * query_limit, query_limit, where);
+
+ // Show error model.
+ debug (relation.to_string ());
+
+ var columns = data_view.columns;
+ uint n = data_view.columns.get_n_items ();
+ for (uint i = 0; i < n; i++) {
+ var col = columns.get_item (i) as Gtk.ColumnViewColumn;
+ if (i >= relation.cols) {
+ col.set_visible (false);
+ continue;
+ }
+
+ switch (relation.get_column_type ((int) i)) {
+ case Type.BOOLEAN, Type.INT64, Type.FLOAT, Type.DOUBLE:
+ var constexprs = new Gtk.ConstantExpression (Type.INT, i);
+ var expresion = new Gtk.CClosureExpression (Type.INT64, null, { constexprs }, (Callback) get_col_by_index_int, null, null);
+
+ var sorter = new Gtk.NumericSorter (expresion);
+
+ col.set_sorter (sorter);
+ break;
+
+ default:
+ var constexprs = new Gtk.ConstantExpression (Type.INT, i);
+ var expresion = new Gtk.CClosureExpression (Type.STRING, null, { constexprs }, (Callback) get_col_by_index, null, null);
+
+ var sorter = new Gtk.StringSorter (expresion);
+
+ col.set_sorter (sorter);
+ break;
+ }
+
+
+ col.set_title (relation.get_header ((int) i));
+ col.set_visible (true);
+ }
+
+ this.sort_model.set_sorter (data_view.get_sorter ());
+
+ TimePerf.begin ();
+ model.clear ();
+ foreach (var item in relation) {
+ model.add (item);
+ }
+
+ TimePerf.end ();
+ } catch (PsequelError.QUERY_FAIL err) {
+ create_dialog ("Query Fail", err.message).present ();
+ }
+ }
+
+ [GtkChild]
+ private unowned Gtk.ColumnView data_view;
+
+ [GtkChild]
+ private unowned Gtk.Entry filter_entry;
+
+ [GtkChild]
+ private unowned Gtk.Button filter_btn;
+
+ [GtkChild]
+ private unowned Gtk.Button left_page;
+
+ [GtkChild]
+ private unowned Gtk.Button right_page;
+
+ [GtkChild]
+ private unowned Gtk.Label status_label;
+ }
+
+ /*
+ */
+ public string get_col_by_index (Relation.Row row, int index) {
+ return row[index];
+ }
+
+ public int64 get_col_by_index_int (Relation.Row row, int index) {
+ return int64.parse (row[index], 10);
+ }
+}
\ No newline at end of file