Cross-platform C SDK logo

Cross-platform C SDK

Products

❮ Back
Next ❯
This page has been automatically translated using the Google Translate API services. We are working on improving texts. Thank you for your understanding and patience.

In this project we will face the construction of an application that allows browsing through a database of products obtained from a Web server (Figure 1). This client-server pattern is widely used today, so we will have a stable base to create any application based on this model. The source code is in folder /src/demo/products of the SDK distribution.

Capture of the Windows version of the application Products.
Figure 1: Application Products, Windows version.
Capture of the macOS version of the application Products.
Figure 2: macOS version.
Capture of the Linux version of the application Products.
Figure 3: Linux/GTK+ version.

1. Specifications

  • The database is remote and we will access it through Web services that will encapsulate the data in JSON. To obtain the products we will use this service and to register a user this other. We have four users registered in our database: amanda, brenda, brian and john all with password 1234.
  • The remote database is read-only. We do not have web services to edit it.
  • The moment a user registers, all articles will automatically be downloaded.
  • A small graph with the sales statistics of each product will be displayed.
  • You can edit the database locally, as well as add or delete records.
  • You can export the local database to disk, as well as import it.
  • We will have the typical navigation controls: First, last, next, previous.
  • We can establish a filter by description. Only those products whose description matches partially with the filter will be displayed.
  • The interface will be in seven languages: English, Spanish, Portuguese, Italian, Vietnamese, Russian and Japanese. We can change the language without closing the application.
  • The application must run on Windows, macOS and Linux.

2. Model-View-Controller

Since this program has a medium level of complexity, we will fragment it into three parts using the well-known pattern model-view-controller MVC (Figure 4).

Outline of the Model-View-Controller pattern.
Figure 4: MVC modules that make up the application.
  • Model: It will deal with the data itself, the connection with the server and the reading/writing on disk. It will be implemented in prmodel.c.
  • View: Here we will implement the data presentation layer, composed of the main window (in prview.c) and the menu bar (in prmenu.c).
  • Controller: Will take care of the logic of the program prctrl.c. It will respond to user events and maintain consistency between the model and the view. Due to the amount of extra work involved in synchronizing each field of the structure with the interface controls, we will use the pattern Model-View-ViewModel MVVM where the model data will be automatically synchronized with the interface and the I/O channels.
  • Main: module products.c. It contains the function osmain and load the three previous actors.

3. Model

The data model of this application is quite simple (Listing 1), since it only requires manipulating an array of structures of type Product.

Listing 1: Structures that make up the data model.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
typedef struct _model_t Model;
typedef struct _product_t Product;

typedef enum _type_t
{
    ekCPU,
    ekGPU,
    ekHDD,
    ekSCD
} type_t;

struct _product_t
{
    type_t type;
    String *code;
    String *description;
    Image *image64;
    real32_t price;
};

struct _model_t
{
    ArrSt(uint32_t) *filter;
    ArrPt(Product) *products;
};

As a previous step, we will register the model structures which will allow us to automate I/O tasks without having to explicitly coding them thanks to Data binding (Listing 2).

Listing 2: Registration of data model struct fields.
1
2
3
4
5
6
7
8
9
dbind_enum(type_t, ekCPU);
dbind_enum(type_t, ekGPU);
dbind_enum(type_t, ekHDD);
dbind_enum(type_t, ekSCD);
dbind(Product, type_t, type);
dbind(Product, String*, code);
dbind(Product, String*, description);
dbind(Product, Image*, image64);
dbind(Product, real32_t, price);

3.1. JSON WebServices

We will get the articles data from the Web server in two steps. On the one hand we will download a Stream with the JSON using HTTP and, later, we will parse it to a C object (Listing 3).

Listing 3: JSON data download and processing.
1
2
3
4
5
6
7
8
wserv_t model_webserv(Model *model)
{
    Stream *stm = http_dget("serv.nappgui.com",80,"/dproducts.php",NULL);
    if (stm != NULL)
    {
        PJson *json = json_read(stm, NULL, PJson);
        stm_close(&stm);
        ...

The JSON of this web service consists of a header and a list of products (Listing 4), so we must register a new structure in order to json_read can create the object correctly (Listing 5). Note that JSON-C pairing is carried out by the field name, so these must be identical (Figure 5).

Listing 4: Web service format.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
{
    "code":0,
    "size":80,
    "data":[
    {"id":0,
    "code":"i7-8700K",
    "description":"Intel BX80684I78700K 8th Gen Core i7-8700K Processor",
    type":0,
    "price":374.8899999999999863575794734060764312744140625,
    "image":"cpu_00.jpg",
    "image64":"\/9j\/4AAQSkZJRgABAQ....
    },
    ...
}
Listing 5: JSON header registration.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
typedef struct _pjson_t PJson;
struct _pjson_t
{
    int32_t code;
    uint32_t size;
    ArrPt(Product) *data;
};

dbind(PJson, int32_t, code);
dbind(PJson, uint32_t, size);
dbind(PJson, ArrPt(Product)*, data);
Schematic showing the automatic linking between a JSON and the structures of the program.
Figure 5: json_read access dbind registry to create a C object from a JSON stream.

3.2. Write/Read on disk

Serialization (Listing 6) and de-serialization (Listing 7) of objects using binary streams can also be performed automatically simply by registering the data types (Figure 6). We do not need to explicitly program reading and writing class methods.

Listing 6: Export of the database to disk.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
bool_t model_export(Model *model, const char_t *pathname, ferror_t *err)
{
    Stream *stm = stm_to_file(pathname, err);
    if (stm != NULL)
    {
        dbind_write(stm, model->products, ArrPt(Product));
        stm_close(&stm);
        return TRUE;
    }

    return FALSE;
}
Listing 7: Importing the database from disk.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
bool_t model_import(Model *model, const char_t *pathname, ferror_t *err)
{
    Stream *stm = stm_from_file(pathname, err);
    if (stm != NULL)
    {
        ArrPt(Product) *products = dbind_read(stm, ArrPt(Product));
        stm_close(&stm);

        if (products != NULL)
        {
            dbind_destroy(&model->products, ArrPt(Product));
            model->products = products;
            return TRUE;
        }
    }

    return FALSE;
}
Schema showing how, thanks to data linking, objects can be serialized without the need for additional code.
Figure 6: (De)serialization of binary objects by dbind.

3.3. Add/Delete records

And finally we will see how to add or delete records to the database using the constructors and destructors provided dbind by default. In (Listing 8) we create a new article and in (Listing 9) we destroy another existing one from its index.

Listing 8: Default constructor.
1
2
3
4
5
void model_add(Model *model)
{
    Product *product = dbind_create(Product);
    arrpt_append(model->products, product, Product);
}
Listing 9: Destructor.
1
2
3
4
5
6
7
8
9
static void i_destroy(Product **product)
{
    dbind_destroy(product, Product);
}

void model_delete(Model *model, const uint32_t index)
{
    arrpt_delete(model->products, index, i_destroy, Product);
}

4. View

We have fragmented the design of the main window into several blocks, each one implemented in its own sublayout. In Use of sublayouts and Sub-layouts you have examples about it. We start with a layout of a column and two rows (Listing 10) (Figure 7). In the upper cell we will place a sublayout with two other cells horizontally: one for the form and one for the login panel. The lower cell will be used for the status bar.

Listing 10: Composition of the main layout.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
static Layout *i_layout(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 2);
    Layout *layout0 = layout_create(2, 1);
    Layout *layout1 = i_form(ctrl);
    Layout *layout2 = i_status_bar(ctrl);
    Panel *panel1 = i_login_panel(ctrl);
    layout_layout(layout0, layout1, 0, 0);
    layout_panel(layout0, panel1, 1, 0);
    layout_layout(layout, layout0, 0, 0);
    layout_layout(layout, layout2, 0, 1);
    return layout;
}
Schematic showing the block composition of the main layout.
Figure 7: Main window layout.

In turn, the layout that integrates the form, implemented in i_form(), is composed of three cells in vertical (Figure 8): One for the toolbar i_toolbar(), another for the selection slider and another for the article data i_product(). This last cell is a sublayout of two columns and three rows. In the central row we locate the labels Type and Price and, in the other two, four sublayout created by the functions i_code_desc() , i_n_img(), i_type() and i_price().

Schematic showing the composition by blocks of the form layout.
Figure 8: Layout que implementa el formulario.

If we look at the code of i_product(), reproduced partially in (Listing 11), we have made a Layout format, assigning a minimum width and height for the upper cells. We also indicate that the vertical expansion is performed on row 0, avoiding the expansion of rows 1 and 2, corresponding to the label, the radiobutton and the price.

Listing 11: Format of layout i_product()).
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
static Layout *i_product()
{
    Layout *layout = layout_create(2, 3);
    ...
    layout_hsize(layout, 0, 200.f);
    layout_hsize(layout, 1, 200.f);
    layout_vsize(layout, 0, 200.f);
    layout_vexpand(layout, 0);
    ...
}

4.1. Multi-layout panel

For user login we have used a panel with two different layouts: One for registration and another to show user data once registered (Listing 12) (Figure 9). This way, the controller can easily switch between them by calling panel_visible_layout. This function will be responsible for displaying/hiding controls and recalculating the size of the window, since it may have suffered variations due to the change in layout.

Listing 12: Creation of a multi-layout panel.
1
2
3
4
5
6
7
8
9
static Panel *i_login_panel(Ctrl *ctrl)
{
    Panel *panel = panel_create();
    Layout *layout0 = i_login(ctrl);
    Layout *layout1 = i_logout(ctrl);
    panel_layout(panel, layout0);
    panel_layout(panel, layout1);
    return panel;
}
Capture of the two layouts that form a multi-layout panel.
Figure 9: Login panel with two layouts.

4.2. Hide columns

It is also possible to hide the login panel through the menu or the corresponding button (Figure 10). This is simple to do inside the controller, acting on the column that contains said panel.

1
layout_show_col(ctrl->layout, 1, state == ekON ? TRUE : FALSE);
Capture that shows how to hide part of the panel.
Figure 10: Show/Hide the login panel.

4.3. Bar graphs

One of the requirements is that the interface includes a small bar chart that shows the sales statistics of each product (Figure 11). The code generated by this graphic is in (Listing 14). In Use of Custom Views, Parametric drawing and 2D Contexts you have more information about interactive graphics.

Listing 14: Parametric drawing of a bar graph.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
static void i_OnStats(Ctrl *ctrl, Event *e)
{
    const EvDraw *params = event_params(e, EvDraw);
    uint32_t i, n = sizeof(ctrl->stats) / sizeof(real32_t);
    real32_t p = 10.f, x = p, y0 = params->height - p;
    real32_t w = (params->width - p * 2) / n;
    real32_t h = params->height - p * 2;
    real32_t avg = 0, pavg;
    char_t tavg[16];
    color_t c[2];
    real32_t stop[2] = {0, 1};
    c[0] = kHOLDER;
    c[1] = kCOLOR_VIEW;
    draw_fill_linear(params->ctx, c,stop, 2, 0, p, 0, params->height - p + 1);

    for (i = 0; i < n; ++i)
    {
        real32_t hr = h * (ctrl->stats[i] / i_MAX_STATS);
        real32_t y = p + h - hr;
        draw_rect(params->ctx, ekFILL, x, y, w - 2, hr);
        avg += ctrl->stats[i];
        x += w;
    }

    avg /= n;
    pavg = h * (avg / i_MAX_STATS);
    pavg = p + h - pavg;
    bstd_sprintf(tavg, sizeof(tavg), "%.2f", avg);
    draw_fill_color(params->ctx, kTXTRED);
    draw_line_color(params->ctx, kTXTRED);
    draw_line(params->ctx, p - 2, pavg, params->width - p, pavg);
    draw_line_color(params->ctx, kCOLOR_LABEL);
    draw_line(params->ctx, p - 2, y0 + 2, params->width - p, y0 + 2);
    draw_line(params->ctx, p - 2, y0 + 2, p - 2, p);
    draw_text(params->ctx, ekFILL, tavg, p, pavg);
}
Animation of a dynamic bar graph, included in the application.
Figure 11: Dynamic graphs in the login panel.

4.4. Translations

The interface has been translated into seven languages, with English as default (Figure 12). To change the language, we call to gui_language within the PopUp event handler (Listing 15). In Resources you have a step-by-step guide to locating and translating applications.

Listing 15: Code that changes the language of the program.
1
2
3
4
5
6
static void i_OnLang(Ctrl *ctrl, Event *e)
{
    const EvButton *params = event_params(e, EvButton);
    static const char_t *LANGS[] = { "en_US", "es_ES", "pt_PT", "it_IT", "vi_VN", "ru_RU", "ja_JP" };
    gui_language(LANGS[params->index]);
}
Animation of the program, changing the interface language.
Figure 12: Automatic translations.

4.5. Dark Mode themes

NAppGUI uses native interface controls, which causes windows to integrate seamlessly with the active desktop theme on each machine. However, if we use custom icons or colors, these may not always be consistent when porting to other systems.


5. Controller

The controller is responsible for maintaining consistency between the Model and the View, as well as for implementing the business logic. Specifically, this program does virtually nothing with the data, regardless of downloading and displaying, which presents a good opportunity to practice.

5.1. Multi-threaded login

When the user presses the button [Login] the program calls two Web services. One to register the user and another to download the data. This process lasts about a second, which is an eternity from the point of view of a process. During this time you will come to appreciate that the program remains "frozen" waiting for the calls to the server to be resolved. This occurs because a "slow" task is running on the same thread that manages the program message loop (Figure 14)(a).

To avoid this unpleasant effect, which can be aggravated if the request lasts longer, we will use Multi-threaded tasks by osapp_task (Listing 19) (Figure 14)(b). This creates a new execution thread that begins in i_login_begin. At the time the data has been downloaded, the NAppGUI task manager will call i_login_end (already in the main thread) and the program will continue with its (mono-thread) execution.

Listing 19: Multi-thread login process.
1
2
3
4
5
6
7
static void i_OnLogin(Ctrl *ctrl, Event *e)
{
    ctrl->status = ekIN_LOGIN;
    i_status(ctrl);
    osapp_task(ctrl, 0., i_login_begin, NULL, i_login_end, Ctrl);
    unref(e);
}
Diagram showing how two threads are coordinated to perform the login process.
Figure 14: Execution of a "slow" task. Single-thread (a), Multi-thread (b). With a single thread the interface will be "frozen".

5.2. Synchronize Model and View

Keeping the Data Model and the View synchronized is also the controller's task. As the user interacts with the interface, it must capture the events, filter data and update the model objects. Similarly, every time the model changes it has to refresh the interface. This bidirectional synchronization can be done using dbind, saving a lot of extra programming code (Figure 15).

Scheme showing the location of the data link in the MVC pattern.
Figure 15: DBind helps the controller in the recurring task of synchronizing objects with the interface.

The implementation of this MVVM pattern Model-View-ViewModel is quite simple and we have it summarized in (Listing 20) (Figure 16).

  • Use cell_dbind to link a layout cell with a model field.
  • Use layout_dbind to link the layout containing the previous cells with the struct which contains the fields.
  • Use layout_dbind_obj to assign an object to the layout. From here the Model-View updates will be made automatically.
  • Listing 20: Binding struct with layout.
     1
     2
     3
     4
     5
     6
     7
     8
     9
    10
    11
    12
    13
    
    // In View
    Cell *cell0 = layout_cell(layout, 0, 1);
    ...
    cell_dbind(cell0, Product, String*, code);
    cell_dbind(cell1, Product, String*, description);
    cell_dbind(cell2, Product, type_t, type);
    cell_dbind(cell3, Product, Image*, image64);
    cell_dbind(cell4, Product, real32_t, price);
    layout_dbind(layout, Product);
    
    // In Controller
    Product *product = model_product(model, index);
    layout_dbind_obj(layout, product, Product);
    
    Diagram showing the correspondence between the fields of a structure and interface controls.
    Figure 16: Data binding in GUI.

It is common for data to be reviewed (filtered) after editing to verify that the values are consistent with the model. dbind supports different formats for registered fields. In (Listing 21) we have applied formatting to the field pricefrom Product.

Listing 21: Field format price from Product.
1
2
3
4
5
dbind_default(Product, real32_t, price, 1);
dbind_range(Product, real32_t, price, .50f, 1e6f);
dbind_precision(Product, real32_t, price, .05f);
dbind_increment(Product, real32_t, price, 5.f);
dbind_suffix(Product, real32_t, price, "€");

5.3. Change the image

To change the image associated with the product, the controller has slightly modified the operation of the ImageView, which will show an edit icon each time the mouse is placed on top of the image (Listing 22), (Figure 17).

Listing 22: Drawing an overlay when the mouse is over the image.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
static void i_OnImgDraw(Ctrl *ctrl, Event *e)
{
    const EvDraw *params = event_params(e, EvDraw);
    const Image *image = gui_respack_image(EDIT_PNG);
    uint32_t w, h;
    image_size(image, &w, &h);
    draw_image(params->context, image, params->width - w - 10, params->height - h - 10);
    unref(ctrl);
}
...
imageview_OnOverDraw(view, listener(ctrl, i_OnImgDraw, Ctrl));
Image view with an icon.
Figure 17: Superimposed icon on image control.

Clicking on the image will open the file opening dialog that will allow us to select a new one. If the dialog is accepted, the image will be loaded and assigned to control (Listing 23). The object will update automatically.

Listing 23: Drawing an overlay when the mouse is over the image.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
static void i_OnImgClick(Ctrl *ctrl, Event *e)
{
    const char_t *type[] = { "png", "jpg" };
    const char_t *file = comwin_open_file(type, 2, NULL);
    if (file != NULL)
    {
        Image *image = image_from_file(file, NULL);
        if (image != NULL)
        {
            View *view = cell_view(ctrl->image_cell);
            imageview_image(view, image);
            image_destroy(&image);
        }
    }
    unref(e);
}
...
imageview_OnClick(view, listener(ctrl, i_OnImgClick, Ctrl));

5.4. Memory management

After closing the program, a report will be printed with the use of memory, alerting us to possible memory leaks (Listing 24). It does not hurt to check it periodically in order to detect anomalies as soon as possible.

Listing 24: Memory usage statistics, generated at the close of any NAppGUI application.
1
2
3
4
5
6
7
8
9
[22:17:21] [OK] Heap Memory Staticstics
[22:17:21] ============================
[22:17:21] Total a/dellocations: 2065, 2065
[22:17:21] Total bytes a/dellocated: 2831766, 2831766
[22:17:21] Max bytes allocated: 1642879
[22:17:21] Effective reallocations: (0/55)
[22:17:21] Real allocations: 13 pages of 65536 bytes
[22:17:21]                   5 pages greater than 65536 bytes
[22:17:21] ============================

If we want more detailed information about the use of memory, we can pass the parameter "-hv" in the options field of osmain (Listing 26).

1
osmain(i_create, i_destroy, "-hv", App)
Listing 26: Detailed output of memory usage.
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
[12:01:41] 'App' a/deallocations: 1, 1 (32) bytes
[12:01:41] 'ArrPt::Cell' a/deallocations: 24, 24 (576) bytes
[12:01:41] 'ArrPt::GuiComponent' a/deallocations: 8, 8 (192) bytes
...
[12:01:41] 'Button' a/deallocations: 13, 13 (1664) bytes
[12:01:41] 'View' a/deallocations: 5, 5 (840) bytes
[12:01:41] 'Clock' a/deallocations: 1, 1 (48) bytes
[12:01:41] 'Combo' a/deallocations: 1, 1 (176) bytes
...
[12:01:41] 'UpDown' a/deallocations: 1, 1 (64) bytes
[12:01:41] 'VImgData' a/deallocations: 4, 4 (160) bytes
[12:01:41] 'Window' a/deallocations: 1, 1 (80) bytes
[12:01:41] 'bool_t::arr' a/deallocations: 6, 6 (27) bytes
[12:01:41] 'i_App' a/deallocations: 1, 1 (184) bytes
[12:01:41] 'i_Task' a/deallocations: 1, 1 (64) bytes

6. The complete program

Listing 27: demo/products/products.hxx
 1
 2
 3
 4
 5
 6
 7
 8
 9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
/* Products Types */

#ifndef __TYPES_HXX__
#define __TYPES_HXX__

#include "gui.hxx"

typedef enum _wserv_t
{
    ekWS_CONNECT = 1,
    ekWS_JSON,
    ekWS_ACCESS,
    ekWS_OK
} wserv_t;

typedef struct _model_t Model;
typedef struct _product_t Product;
typedef struct _ctrl_t Ctrl;

__EXTERN_C

extern color_t kHOLDER;
extern color_t kEDITBG;
extern color_t kSTATBG;
extern color_t kSTATSK;
extern color_t kTXTRED;

__END_C

#endif

Listing 28: demo/products/prmodel.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
/* Products Model */

#include "prmodel.h"
#include "guiall.h"
#include "httpreq.h"
#include "json.h"
#include "res_gui.h"

typedef struct _pjson_t PJson;

typedef enum _type_t
{
    ekCPU,
    ekGPU,
    ekHDD,
    ekSCD
} type_t;

struct _product_t
{
    type_t type;
    String *code;
    String *description;
    Image *image64;
    real32_t price;
};

struct _pjson_t
{
    int32_t code;
    uint32_t size;
    ArrPt(Product) *data;
};

struct _model_t
{
    ArrSt(uint32_t) *filter;
    ArrPt(Product) *products;
};

/*---------------------------------------------------------------------------*/

Model *model_create(void)
{
    Model *model = heap_new(Model);
    model->filter = arrst_create(uint32_t);
    model->products = arrpt_create(Product);
    return model;
}

/*---------------------------------------------------------------------------*/

void model_destroy(Model **model)
{
    arrst_destroy(&(*model)->filter, NULL, uint32_t);
    dbind_destroy(&(*model)->products, ArrPt(Product));
    heap_delete(model, Model);
}

/*---------------------------------------------------------------------------*/

static Stream *i_http_get(void)
{
    Http *http = http_create("serv.nappgui.com", 80, NULL);
    Stream *stm = NULL;
    if (http != NULL)
    {
        uint32_t status = 0;
        http_get(http, "/dproducts.php", NULL, 0, NULL);
        status = http_response_status(http);
        if (status >= 200 && status <= 299)
        {
            stm = stm_memory(4096);
            if (http_response_body(http, stm, NULL) == FALSE)
                stm_close(&stm);
        }

        http_destroy(&http);
    }

    return stm;
}

/*---------------------------------------------------------------------------*/

wserv_t model_webserv(Model *model)
{
    Stream *stm = i_http_get();
    if (stm != NULL)
    {
        PJson *json = json_read(stm, NULL, PJson);
        stm_close(&stm);

        if (json != NULL)
        {
            cassert(json->size == arrpt_size(json->data, Product));
            dbind_destroy(&model->products, ArrPt(Product));
            model->products = json->data;
            json->data = NULL;
            json_destroy(&json, PJson);
            return ekWS_OK;
        }

        return ekWS_JSON;
    }

    return ekWS_CONNECT;
}

/*---------------------------------------------------------------------------*/

bool_t model_import(Model *model, const char_t *pathname, ferror_t *err)
{
    Stream *stm = stm_from_file(pathname, err);
    if (stm != NULL)
    {
        ArrPt(Product) *products = dbind_read(stm, ArrPt(Product));
        stm_close(&stm);

        if (products != NULL)
        {
            dbind_destroy(&model->products, ArrPt(Product));
            model->products = products;
            return TRUE;
        }
    }

    return FALSE;
}

/*---------------------------------------------------------------------------*/

bool_t model_export(Model *model, const char_t *pathname, ferror_t *err)
{
    Stream *stm = stm_to_file(pathname, err);
    if (stm != NULL)
    {
        dbind_write(stm, model->products, ArrPt(Product));
        stm_close(&stm);
        return TRUE;
    }

    return FALSE;
}

/*---------------------------------------------------------------------------*/

uint32_t model_count(const Model *model)
{
    uint32_t total = arrst_size(model->filter, uint32_t);
    if (total == 0)
        total = arrpt_size(model->products, Product);
    return total;
}

/*---------------------------------------------------------------------------*/

void model_clear(Model *model)
{
    dbind_destroy(&model->products, ArrPt(Product));
    arrst_clear(model->filter, NULL, uint32_t);
    model->products = dbind_create(ArrPt(Product));
}

/*---------------------------------------------------------------------------*/

void model_add(Model *model)
{
    Product *product = dbind_create(Product);
    arrpt_append(model->products, product, Product);
    arrst_clear(model->filter, NULL, uint32_t);
}

/*---------------------------------------------------------------------------*/

static uint32_t i_index(ArrSt(uint32_t) *filter, const uint32_t index)
{
    if (arrst_size(filter, uint32_t) > 0)
        return *arrst_get(filter, index, uint32_t);
    else
        return index;
}

/*---------------------------------------------------------------------------*/

static __INLINE void i_destroy(Product **product)
{
    dbind_destroy(product, Product);
}

/*---------------------------------------------------------------------------*/

void model_delete(Model *model, const uint32_t index)
{
    uint32_t lindex = i_index(model->filter, index);
    arrpt_delete(model->products, lindex, i_destroy, Product);
    arrst_clear(model->filter, NULL, uint32_t);
}

/*---------------------------------------------------------------------------*/

bool_t model_filter(Model *model, const char_t *filter)
{
    ArrSt(uint32_t) *new_filter = arrst_create(uint32_t);

    arrpt_foreach(product, model->products, Product)
        if (str_str(tc(product->description), filter) != NULL)
            arrst_append(new_filter, product_i, uint32_t);
    arrpt_end();

    arrst_destroy(&model->filter, NULL, uint32_t);
    model->filter = new_filter;

    return (bool_t)(arrst_size(new_filter, uint32_t) > 0);
}

/*---------------------------------------------------------------------------*/

Product *model_product(Model *model, const uint32_t product_id)
{
    uint32_t lindex = i_index(model->filter, product_id);
    return arrpt_get(model->products, lindex, Product);
}

/*---------------------------------------------------------------------------*/

void model_bind(void)
{
    dbind_enum(type_t, ekCPU);
    dbind_enum(type_t, ekGPU);
    dbind_enum(type_t, ekHDD);
    dbind_enum(type_t, ekSCD);
    dbind(Product, type_t, type);
    dbind(Product, String*, code);
    dbind(Product, String*, description);
    dbind(Product, Image*, image64);
    dbind(Product, real32_t, price);
    dbind(PJson, int32_t, code);
    dbind(PJson, uint32_t, size);
    dbind(PJson, ArrPt(Product)*, data);
    dbind_default(Product, real32_t, price, 1);
    dbind_range(Product, real32_t, price, .50f, 1e6f);
    dbind_precision(Product, real32_t, price, .05f);
    dbind_increment(Product, real32_t, price, 5.f);
    dbind_suffix(Product, real32_t, price, "€");
    dbind_default(Product, Image*, image64, gui_image(NOIMAGE_PNG));
}

/*---------------------------------------------------------------------------*/

void model_layout(Layout *layout)
{
    layout_dbind(layout, NULL, Product);
}

/*---------------------------------------------------------------------------*/

void model_type(Cell *cell)
{
    cell_dbind(cell, Product, type_t, type);
}

/*---------------------------------------------------------------------------*/

void model_code(Cell *cell)
{
    cell_dbind(cell, Product, String*, code);
}

/*---------------------------------------------------------------------------*/

void model_desc(Cell *cell)
{
    cell_dbind(cell, Product, String*, description);
}

/*---------------------------------------------------------------------------*/

void model_image(Cell *cell)
{
    cell_dbind(cell, Product, Image*, image64);
}

/*---------------------------------------------------------------------------*/

void model_price(Cell *cell)
{
    cell_dbind(cell, Product, real32_t, price);
}
Listing 29: demo/products/prview.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
/* Products View */

#include "prview.h"
#include "prctrl.h"
#include "guiall.h"
#include "res_gui.h"

/*---------------------------------------------------------------------------*/

static Layout *i_toolbar(Ctrl *ctrl)
{
    Layout *layout = layout_create(8, 1);
    Button *button0 = button_flat();
    Button *button1 = button_flat();
    Button *button2 = button_flat();
    Button *button3 = button_flat();
    Button *button4 = button_flat();
    Button *button5 = button_flat();
    Button *button6 = button_flatgle();
    Combo *combo = combo_create();
    button_text(button0, TWIN_FIRST);
    button_text(button1, TWIN_BACK);
    button_text(button2, TWIN_NEXT);
    button_text(button3, TWIN_LAST);
    button_text(button4, TWIN_ADD);
    button_text(button5, TWIN_DEL);
    button_text(button6, TWIN_SETTINGS_PANEL);
    combo_tooltip(combo, TWIN_FILTER_DESC);
    combo_bgcolor_focus(combo, kEDITBG);
    combo_phtext(combo, TWIN_FILTER);
    combo_phcolor(combo, kHOLDER);
    combo_phstyle(combo, ekFITALIC | ekFUNDERLINE);
    layout_button(layout, button0, 0, 0);
    layout_button(layout, button1, 1, 0);
    layout_button(layout, button2, 2, 0);
    layout_button(layout, button3, 3, 0);
    layout_button(layout, button4, 4, 0);
    layout_button(layout, button5, 5, 0);
    layout_combo(layout, combo, 6, 0);
    layout_button(layout, button6, 7, 0);
    layout_hmargin(layout, 5, 5);
    layout_hmargin(layout, 6, 5);
    layout_hexpand(layout, 6);
    ctrl_first_cell(ctrl, layout_cell(layout, 0, 0));
    ctrl_back_cell(ctrl, layout_cell(layout, 1, 0));
    ctrl_next_cell(ctrl, layout_cell(layout, 2, 0));
    ctrl_last_cell(ctrl, layout_cell(layout, 3, 0));
    ctrl_add_cell(ctrl, layout_cell(layout, 4, 0));
    ctrl_minus_cell(ctrl, layout_cell(layout, 5, 0));
    ctrl_filter_cell(ctrl, layout_cell(layout, 6, 0));
    ctrl_setting_cell(ctrl, layout_cell(layout, 7, 0));
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_code_desc(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 4);
    Label *label0 = label_create();
    Label *label1 = label_create();
    Edit *edit0 = edit_create();
    Edit *edit1 = edit_multiline();
    label_text(label0, TWIN_CODE);
    label_text(label1, TWIN_DESC);
    edit_phtext(edit0, TWIN_TYPE_CODE);
    edit_phtext(edit1, TWIN_TYPE_DESC);
    edit_bgcolor_focus(edit0, kEDITBG);
    edit_bgcolor_focus(edit1, kEDITBG);
    edit_phcolor(edit0, kHOLDER);
    edit_phcolor(edit1, kHOLDER);
    edit_phstyle(edit0, ekFITALIC | ekFUNDERLINE);
    edit_phstyle(edit1, ekFITALIC | ekFUNDERLINE);
    layout_label(layout, label0, 0, 0);
    layout_edit(layout, edit0, 0, 1);
    layout_label(layout, label1, 0, 2);
    layout_edit(layout, edit1, 0, 3);
    layout_vmargin(layout, 1, 10);
    layout_vexpand(layout, 3);
    ctrl_code_cell(ctrl, layout_cell(layout, 0, 1));
    ctrl_desc_cell(ctrl, layout_cell(layout, 0, 3));
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_type(void)
{
    Layout *layout = layout_create(4, 1);
    Button *button0 = button_radio();
    Button *button1 = button_radio();
    Button *button2 = button_radio();
    Button *button3 = button_radio();
    button_text(button0, TWIN_CPU);
    button_text(button1, TWIN_GPU);
    button_text(button2, TWIN_HDD);
    button_text(button3, TWIN_SCD);
    layout_button(layout, button0, 0, 0);
    layout_button(layout, button1, 1, 0);
    layout_button(layout, button2, 2, 0);
    layout_button(layout, button3, 3, 0);
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_n_img(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 2);
    Label *label = label_create();
    ImageView *view = imageview_create();
    label_align(label, ekCENTER);
    layout_halign(layout, 0, 0, ekJUSTIFY);
    layout_label(layout, label, 0, 0);
    layout_imageview(layout, view, 0, 1);
    layout_vexpand(layout, 1);
    ctrl_counter_cell(ctrl, layout_cell(layout, 0, 0));
    ctrl_image_cell(ctrl, layout_cell(layout, 0, 1));
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_price(void)
{
    Layout *layout = layout_create(2, 1);
    Edit *edit = edit_create();
    Font *font = font_system(18, ekFBOLD);
    UpDown *updown = updown_create();
    edit_phtext(edit, TWIN_TYPE_PRICE);
    edit_font(edit, font);
    edit_align(edit, ekRIGHT);
    edit_color(edit, kTXTRED);
    edit_bgcolor_focus(edit, kEDITBG);
    edit_phcolor(edit, kHOLDER);
    edit_phstyle(edit, ekFITALIC | ekFUNDERLINE);
    layout_edit(layout, edit, 0, 0);
    layout_updown(layout, updown, 1, 0);
    layout_hsize(layout, 1, 24);
    layout_hexpand(layout, 0);
    font_destroy(&font);
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_product(Ctrl *ctrl)
{
    Layout *layout = layout_create(2, 3);
    Layout *layout0 = i_code_desc(ctrl);
    Layout *layout1 = i_type();
    Layout *layout2 = i_n_img(ctrl);
    Layout *layout3 = i_price();
    Label *label0 = label_create();
    Label *label1 = label_create();
    label_text(label0, TWIN_TYPE);
    label_text(label1, TWIN_PRICE);
    layout_layout(layout, layout0, 0, 0);
    layout_label(layout, label0, 0, 1);
    layout_layout(layout, layout1, 0, 2);
    layout_layout(layout, layout2, 1, 0);
    layout_label(layout, label1, 1, 1);
    layout_layout(layout, layout3, 1, 2);
    layout_halign(layout, 1, 1, ekRIGHT);
    layout_hsize(layout, 1, 200);
    layout_vsize(layout, 0, 200);
    layout_hmargin(layout, 0, 10);
    layout_vmargin(layout, 0, 10);
    layout_margin4(layout, 0, 10, 10, 10);
    layout_vexpand(layout, 0);
    ctrl_type_cell(ctrl, layout_cell(layout, 0, 2));
    ctrl_price_cell(ctrl, layout_cell(layout, 1, 2));
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_form(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 3);
    Layout *layout0 = i_toolbar(ctrl);
    Layout *layout1 = i_product(ctrl);
    Slider *slider = slider_create();
    Cell *cell = NULL;
    layout_layout(layout, layout0, 0, 0);
    layout_slider(layout, slider, 0, 1);
    layout_layout(layout, layout1, 0, 2);
    layout_vexpand(layout, 2);
    cell = layout_cell(layout, 0, 1);
    cell_padding4(cell, 0, 10, 0, 10);
    ctrl_slider_cell(ctrl, cell);    
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_login(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 10);
    Label *label0 = label_create();
    Label *label1 = label_multiline();
    Label *label2 = label_create();
    Label *label3 = label_create();
    PopUp *popup0 = popup_create();
    ImageView *view0 = imageview_create();
    Edit *edit0 = edit_create();
    Edit *edit1 = edit_create();
    Button *button = button_push();
    label_text(label0, TWIN_SETLANG);
    label_text(label1, TWIN_LOGIN_MSG);
    label_text(label2, TWIN_USER);
    label_text(label3, TWIN_PASS);
    popup_add_elem(popup0, ENGLISH, (const Image*)USA_PNG);
    popup_add_elem(popup0, SPANISH, (const Image*)SPAIN_PNG);
    popup_add_elem(popup0, PORTUGUESE, (const Image*)PORTUGAL_PNG);
    popup_add_elem(popup0, ITALIAN, (const Image*)ITALY_PNG);
    popup_add_elem(popup0, VIETNAMESE, (const Image*)VIETNAM_PNG);
    popup_add_elem(popup0, RUSSIAN, (const Image*)RUSSIA_PNG);
    popup_add_elem(popup0, JAPANESE, (const Image*)JAPAN_PNG);
    popup_tooltip(popup0, TWIN_SETLANG);   
    imageview_image(view0, (const Image*)USER_PNG);
    edit_passmode(edit1, TRUE);
    button_text(button, TWIN_LOGIN);
    layout_label(layout, label0, 0, 0);
    layout_popup(layout, popup0, 0, 1);
    layout_label(layout, label1, 0, 2);
    layout_imageview(layout, view0, 0, 3);
    layout_label(layout, label2, 0, 4);
    layout_edit(layout, edit0, 0, 5);
    layout_label(layout, label3, 0, 6);
    layout_edit(layout, edit1, 0, 7);
    layout_button(layout, button, 0, 9);
    layout_vmargin(layout, 0, 5);
    layout_vmargin(layout, 1, 10);
    layout_vmargin(layout, 2, 10);
    layout_vmargin(layout, 5, 5);
    layout_vmargin(layout, 8, 5);
    layout_margin4(layout, 5, 10, 10, 10);
    layout_hsize(layout, 0, 200);
    layout_vexpand(layout, 8);
    ctrl_lang_cell(ctrl, layout_cell(layout, 0, 1));
    ctrl_user_cell(ctrl, layout_cell(layout, 0, 5));
    ctrl_pass_cell(ctrl, layout_cell(layout, 0, 7));
    ctrl_login_cell(ctrl, layout_cell(layout, 0, 9));
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_logout(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 6);
    ImageView *view = imageview_create();
    Label *label0 = label_create();
    Label *label1 = label_create();
    View *cview = view_create();
    Button *button = button_push();
    label_align(label0, ekCENTER);
    label_align(label1, ekCENTER);
    button_text(button, TWIN_LOGOUT);
    view_size(cview, s2df(160, 160));
    layout_imageview(layout, view, 0, 0);
    layout_label(layout, label0, 0, 1);
    layout_label(layout, label1, 0, 2);
    layout_view(layout, cview, 0, 3);
    layout_button(layout, button, 0, 5);
    layout_halign(layout, 0, 1, ekJUSTIFY);
    layout_halign(layout, 0, 2, ekJUSTIFY);
    layout_halign(layout, 0, 3, ekCENTER);
    layout_vmargin(layout, 0, 5);
    layout_vmargin(layout, 2, 5);
    layout_vexpand(layout, 4);
    layout_hsize(layout, 0, 200);
    layout_margin(layout, 10);
    ctrl_stats_cell(ctrl, layout_cell(layout, 0, 3));
    ctrl_logout_cell(ctrl, layout_cell(layout, 0, 5));
    return layout;
}

/*---------------------------------------------------------------------------*/

static Panel *i_login_panel(Ctrl *ctrl)
{
    Panel *panel = panel_create();
    Layout *layout0 = i_login(ctrl);
    Layout *layout1 = i_logout(ctrl);
    panel_layout(panel, layout0);
    panel_layout(panel, layout1);
    ctrl_login_panel(ctrl, panel);
    return panel;
}

/*---------------------------------------------------------------------------*/

static Layout *i_status_bar(Ctrl *ctrl)
{
    Layout *layout = layout_create(2, 1);
    ImageView *view = imageview_create();
    Label *label = label_create();
    imageview_size(view, s2df(16, 16));
    layout_imageview(layout, view, 0, 0);
    layout_label(layout, label, 1, 0);
    layout_halign(layout, 1, 0, ekJUSTIFY);
    layout_hexpand(layout, 1);
    layout_hmargin(layout, 0, 5);
    layout_margin(layout, 5);
    layout_bgcolor(layout, kSTATBG);
    layout_skcolor(layout, kSTATSK);
    ctrl_status_layout(ctrl, layout);
    return layout;
}

/*---------------------------------------------------------------------------*/

static Layout *i_layout(Ctrl *ctrl)
{
    Layout *layout = layout_create(1, 2);
    Layout *layout0 = layout_create(2, 1);
    Layout *layout1 = i_form(ctrl);
    Layout *layout2 = i_status_bar(ctrl);
    Panel *panel1 = i_login_panel(ctrl);
    layout_layout(layout0, layout1, 0, 0);
    layout_panel(layout0, panel1, 1, 0);
    layout_layout(layout, layout0, 0, 0);
    layout_layout(layout, layout2, 0, 1);
    ctrl_main_layout(ctrl, layout0);
    return layout;
}

/*---------------------------------------------------------------------------*/

Window *prview_create(Ctrl *ctrl)
{
    Panel *panel = panel_create();
    Layout *layout = i_layout(ctrl);
    Window *window = NULL;
    ctrl_theme_images(ctrl);
    panel_layout(panel, layout);
    window = window_create(ekWNSTD, &panel);
    window_title(window, TWIN_TITLE);
    ctrl_window(ctrl, window);
    return window;
}
Listing 30: demo/products/prmenu.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
/* Products Menu */

#include "prmenu.h"
#include "prctrl.h"
#include "guiall.h"
#include "res_gui.h"

/*---------------------------------------------------------------------------*/

#if defined (__APPLE__)
static Menu *i_app(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    MenuItem *item1 = menuitem_separator();
    MenuItem *item2 = menuitem_create();
    MenuItem *item3 = menuitem_separator();
    MenuItem *item4 = menuitem_create();
    menuitem_text(item0, TMEN_ABOUT);
    menuitem_text(item2, TMEN_PREFERS);
    menuitem_text(item4, TMEN_QUIT);
    menu_item(menu, item0);
    menu_item(menu, item1);
    menu_item(menu, item2);
    menu_item(menu, item3);
    menu_item(menu, item4);
    ctrl_about_item(ctrl, item0);
    ctrl_exit_item(ctrl, item4);
    return menu;
}
#endif

/*---------------------------------------------------------------------------*/

static Menu *i_file(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    MenuItem *item1 = menuitem_create();
    menuitem_text(item0, TMEN_IMPORT);
    menuitem_text(item1, TMEN_EXPORT);
    menu_item(menu, item0);
    menu_item(menu, item1);

#if !defined(__APPLE__)
    {
        MenuItem *item2 = menuitem_separator();
        MenuItem *item3 = menuitem_create();
        menuitem_text(item3, TMEN_EXIT);
        menuitem_image(item3, (const Image*)EXIT_PNG);
        menu_item(menu, item2);
        menu_item(menu, item3);
        ctrl_exit_item(ctrl, item3);
    }
#endif

    ctrl_import_item(ctrl, item0);
    ctrl_export_item(ctrl, item1);
    return menu;
}

/*---------------------------------------------------------------------------*/

static Menu *i_navigate(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    MenuItem *item1 = menuitem_create();
    MenuItem *item2 = menuitem_create();
    MenuItem *item3 = menuitem_create();
    menuitem_text(item0, TMEN_FIRST);
    menuitem_text(item1, TMEN_BACK);
    menuitem_text(item2, TMEN_NEXT);
    menuitem_text(item3, TMEN_LAST);
    menuitem_key(item0, ekKEY_F5, 0);
    menuitem_key(item1, ekKEY_F6, 0);
    menuitem_key(item2, ekKEY_F7, 0);
    menuitem_key(item3, ekKEY_F8, 0);
    menu_item(menu, item0);
    menu_item(menu, item1);
    menu_item(menu, item2);
    menu_item(menu, item3);
    ctrl_first_item(ctrl, item0);
    ctrl_back_item(ctrl, item1);
    ctrl_next_item(ctrl, item2);
    ctrl_last_item(ctrl, item3);
    return menu;
}

/*---------------------------------------------------------------------------*/

static Menu *i_view(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    unref(ctrl);
    menuitem_text(item0, TMEN_LOGIN_PANEL);
    menuitem_image(item0, (const Image*)SETTINGS16_PNG);
    menu_item(menu, item0);
    ctrl_setting_item(ctrl, item0);
    return menu;
}

/*---------------------------------------------------------------------------*/

static Menu *i_server(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    MenuItem *item1 = menuitem_create();
    menuitem_text(item0, TMEN_LOGIN);
    menuitem_text(item1, TMEN_LOGOUT);
    menu_item(menu, item0);
    menu_item(menu, item1);
    ctrl_login_item(ctrl, item0);
    ctrl_logout_item(ctrl, item1);
    return menu;
}

/*---------------------------------------------------------------------------*/

static Menu *i_language(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    MenuItem *item1 = menuitem_create();
    MenuItem *item2 = menuitem_create();
    MenuItem *item3 = menuitem_create();
    MenuItem *item4 = menuitem_create();
    MenuItem *item5 = menuitem_create();
    MenuItem *item6 = menuitem_create();
    menuitem_text(item0, ENGLISH);
    menuitem_text(item1, SPANISH);
    menuitem_text(item2, PORTUGUESE);
    menuitem_text(item3, ITALIAN);
    menuitem_text(item4, VIETNAMESE);
    menuitem_text(item5, RUSSIAN);
    menuitem_text(item6, JAPANESE);
    menuitem_image(item0, (const Image*)USA_PNG);
    menuitem_image(item1, (const Image*)SPAIN_PNG);
    menuitem_image(item2, (const Image*)PORTUGAL_PNG);
    menuitem_image(item3, (const Image*)ITALY_PNG);
    menuitem_image(item4, (const Image*)VIETNAM_PNG);
    menuitem_image(item5, (const Image*)RUSSIA_PNG);
    menuitem_image(item6, (const Image*)JAPAN_PNG);
    menu_item(menu, item0);
    menu_item(menu, item1);
    menu_item(menu, item2);
    menu_item(menu, item3);
    menu_item(menu, item4);
    menu_item(menu, item5);
    menu_item(menu, item6);
    ctrl_lang_menu(ctrl, menu);
    return menu;
}

/*---------------------------------------------------------------------------*/

#if !defined (__APPLE__)
static Menu *i_help(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item0 = menuitem_create();
    menuitem_text(item0, TMEN_ABOUT);
    menuitem_image(item0, (const Image*)ABOUT_PNG);
    menu_item(menu, item0);
    ctrl_about_item(ctrl, item0);
    return menu;
}
#endif

/*---------------------------------------------------------------------------*/

Menu *prmenu_create(Ctrl *ctrl)
{
    Menu *menu = menu_create();
    MenuItem *item1 = menuitem_create();
    MenuItem *item2 = menuitem_create();
    MenuItem *item3 = menuitem_create();
    MenuItem *item4 = menuitem_create();
    MenuItem *item5 = menuitem_create();
    Menu *submenu1 = i_file(ctrl);
    Menu *submenu2 = i_navigate(ctrl);
    Menu *submenu3 = i_view(ctrl);
    Menu *submenu4 = i_server(ctrl);
    Menu *submenu5 = i_language(ctrl);
    
 #if defined (__APPLE__)
    {
        MenuItem *item0 = menuitem_create();
        Menu *submenu0 = i_app(ctrl);
        menuitem_text(item1, "");
        menuitem_submenu(item0, &submenu0);
        menu_item(menu, item0);
    }
#endif

    menuitem_text(item1, TMEN_FILE);
    menuitem_text(item2, TMEN_NAVIGATE);
    menuitem_text(item3, TMEN_VIEW);
    menuitem_text(item4, TMEN_SERVER);
    menuitem_text(item5, LANGUAGE);
    menuitem_submenu(item1, &submenu1);
    menuitem_submenu(item2, &submenu2);
    menuitem_submenu(item3, &submenu3);
    menuitem_submenu(item4, &submenu4);
    menuitem_submenu(item5, &submenu5);
    menu_item(menu, item1);
    menu_item(menu, item2);
    menu_item(menu, item3);
    menu_item(menu, item4);
    menu_item(menu, item5);

 #if !defined (__APPLE__)
    {
        MenuItem *item6 = menuitem_create();
        Menu *submenu6 = i_help(ctrl);
        menuitem_text(item6, TMEN_HELP);
        menuitem_submenu(item6, &submenu6);
        menu_item(menu, item6);
    }
#endif
    return menu;
}
Listing 31: demo/products/prctrl.c
  1
  2
  3
  4
  5
  6
  7
  8
  9
 10
 11
 12
 13
 14
 15
 16
 17
 18
 19
 20
 21
 22
 23
 24
 25
 26
 27
 28
 29
 30
 31
 32
 33
 34
 35
 36
 37
 38
 39
 40
 41
 42
 43
 44
 45
 46
 47
 48
 49
 50
 51
 52
 53
 54
 55
 56
 57
 58
 59
 60
 61
 62
 63
 64
 65
 66
 67
 68
 69
 70
 71
 72
 73
 74
 75
 76
 77
 78
 79
 80
 81
 82
 83
 84
 85
 86
 87
 88
 89
 90
 91
 92
 93
 94
 95
 96
 97
 98
 99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410
411
412
413
414
415
416
417
418
419
420
421
422
423
424
425
426
427
428
429
430
431
432
433
434
435
436
437
438
439
440
441
442
443
444
445
446
447
448
449
450
451
452
453
454
455
456
457
458
459
460
461
462
463
464
465
466
467
468
469
470
471
472
473
474
475
476
477
478
479
480
481
482
483
484
485
486
487
488
489
490
491
492
493
494
495
496
497
498
499
500
501
502
503
504
505
506
507
508
509
510
511
512
513
514
515
516
517
518
519
520
521
522
523
524
525
526
527
528
529
530
531
532
533
534
535
536
537
538
539
540
541
542
543
544
545
546
547
548
549
550
551
552
553
554
555
556
557
558
559
560
561
562
563
564
565
566
567
568
569
570
571
572
573
574
575
576
577
578
579
580
581
582
583
584
585
586
587
588
589
590
591
592
593
594
595
596
597
598
599
600
601
602
603
604
605
606
607
608
609
610
611
612
613
614
615
616
617
618
619
620
621
622
623
624
625
626
627
628
629
630
631
632
633
634
635
636
637
638
639
640
641
642
643
644
645
646
647
648
649
650
651
652
653
654
655
656
657
658
659
660
661
662
663
664
665
666
667
668
669
670
671
672
673
674
675
676
677
678
679
680
681
682
683
684
685
686
687
688
689
690
691
692
693
694
695
696
697
698
699
700
701
702
703
704
705
706
707
708
709
710
711
712
713
714
715
716
717
718
719
720
721
722
723
724
725
726
727
728
729
730
731
732
733
734
735
736
737
738
739
740
741
742
743
744
745
746
747
748
749
750
751
752
753
754
755
756
757
758
759
760
761
762
763
764
765
766
767
768
769
770
771
772
773
774
775
776
777
778
779
780
781
782
783
784
785
786
787
788
789
790
791
792
793
794
795
796
797
798
799
800
801
802
803
804
805
806
807
808
809
810
811
812
813
814
815
816
817
818
819
820
821
822
823
824
825
826
827
828
829
830
831
832
833
834
835
836
837
838
839
840
841
842
843
844
845
846
847
848
849
850
851
852
853
854
855
856
857
858
859
860
861
862
863
864
865
866
867
868
869
870
871
872
873
874
875
876
877
878
879
880
881
882
883
884
885
886
887
888
889
890
891
892
893
894
895
896
897
898
899
900
901
902
903
904
905
906
907
908
909
910
911
912
913
914
915
916
917
918
919
920
921
922
923
924
925
926
927
928
929
930
931
932
933
934
935
936
937
938
939
940
941
942
943
944
945
946
947
948
949
950
951
952
953
954
955
956
957
958
959
960
961
962
963
964
965
966
967
968
969
970
971
972
973
974
975
976
977
978
979
980
981
982
983
984
985
986
987
988
989
990
991
992
993
994
995
996
997
998
999
/* Products Controller */

#include "prctrl.h"
#include "prmodel.h"
#include "nappgui.h"
#include "httpreq.h"
#include "json.h"
#include "res_gui.h"

typedef enum _status_t
{
    ekWAIT_LOGIN,
    ekIN_LOGIN,
    ekERR_LOGIN,
    ekOK_LOGIN
} status_t;

typedef struct _user_t User;
typedef struct _ujson_t UJson;

struct _user_t
{
    String *name;
    String *mail;
    Image *image64;
};

struct _ujson_t
{
    int32_t code;
    User data;
};

struct _ctrl_t
{
    Model *model;
    status_t status;
    wserv_t err;
    uint32_t selected;
    real32_t stats[12];
    UJson *ujson;
    Window *window;
    Layout *main_layout;
    Layout *status_layout;
    Cell *image_cell;
    Cell *first_cell;
    Cell *back_cell;
    Cell *next_cell;
    Cell *last_cell;
    Cell *add_cell;
    Cell *minus_cell;
    Cell *filter_cell;
    Cell *slider_cell;
    Cell *counter_cell;    
    Cell *code_cell;
    Cell *desc_cell;
    Cell *price_cell;    
    Cell *lang_cell;
    Cell *setting_cell;
    Cell *user_cell;
    Cell *pass_cell;
    Cell *login_cell;
    Cell *logout_cell;
    Cell *stats_cell;
    Panel *login_panel;
    Menu *lang_menu;
    MenuItem *import_item;
    MenuItem *export_item;
    MenuItem *first_item;
    MenuItem *back_item;
    MenuItem *next_item;
    MenuItem *last_item;
    MenuItem *setting_item;
    MenuItem *login_item;
    MenuItem *logout_item;
};

/*---------------------------------------------------------------------------*/

static real32_t i_MAX_STATS = 20.f;

/*---------------------------------------------------------------------------*/

Ctrl *ctrl_create(Model *model)
{
    Ctrl *ctrl = heap_new0(Ctrl);
    ctrl->model = model;
    ctrl->status = ekWAIT_LOGIN;
    ctrl->selected = 0;
    dbind(User, String*, name);
    dbind(User, String*, mail);
    dbind(User, Image*, image64);
    dbind(UJson, int32_t, code);
    dbind(UJson, User, data);
    return ctrl;
}

/*---------------------------------------------------------------------------*/

void ctrl_destroy(Ctrl **ctrl)
{
    heap_delete(ctrl, Ctrl);
}

/*---------------------------------------------------------------------------*/

void ctrl_main_layout(Ctrl *ctrl, Layout *layout)
{
    model_layout(layout);
    ctrl->main_layout = layout;
}

/*---------------------------------------------------------------------------*/

void ctrl_status_layout(Ctrl *ctrl, Layout *layout)
{
    ctrl->status_layout = layout;
}

/*---------------------------------------------------------------------------*/

static void i_update_product(Ctrl *ctrl)
{
    uint32_t total = model_count(ctrl->model);
    bool_t enabled = FALSE;
    bool_t is_first = (total == 0 || ctrl->selected == 0) ? TRUE : FALSE;
    bool_t is_last = (total == 0 || ctrl->selected == (total - 1)) ? TRUE : FALSE;
    Slider *slider = cell_slider(ctrl->slider_cell);
    Label *counter = cell_label(ctrl->counter_cell);
    Product *product = NULL;

    if (total > 0)
    {
        char_t msg[64];
        uint32_t i, n = sizeof(ctrl->stats) / sizeof(real32_t);
        View *vstats = cell_view(ctrl->stats_cell);
        product = model_product(ctrl->model, ctrl->selected);
        bstd_sprintf(msg, 64, "[%d/%d]", ctrl->selected + 1, total);
        label_text(counter, msg);
        slider_value(slider, (real32_t)ctrl->selected / (real32_t)(total > 1 ? total - 1 : 1));        
        enabled = TRUE;
        for (i = 0; i < n; ++i)
            ctrl->stats[i] = bmath_randf(2.f, i_MAX_STATS - 2.f);
        view_update(vstats);
    }
    else
    {
        label_text(counter, "[0/0]");
        slider_value(slider, 0.f);
        enabled = FALSE;
    }

    layout_dbind_obj(ctrl->main_layout, product, Product);
    cell_enabled(ctrl->add_cell, enabled);
    cell_enabled(ctrl->minus_cell, enabled);
    cell_enabled(ctrl->slider_cell, enabled);
    cell_enabled(ctrl->filter_cell, enabled);
    cell_enabled(ctrl->first_cell, !is_first);
    cell_enabled(ctrl->back_cell, !is_first);
    cell_enabled(ctrl->next_cell, !is_last);
    cell_enabled(ctrl->last_cell, !is_last);
    menuitem_enabled(ctrl->first_item, !is_first);
    menuitem_enabled(ctrl->back_item, !is_first);
    menuitem_enabled(ctrl->next_item, !is_last);
    menuitem_enabled(ctrl->last_item, !is_last);
}

/*---------------------------------------------------------------------------*/

static void i_status(Ctrl *ctrl)
{
    ImageView *view = layout_get_imageview(ctrl->status_layout, 0, 0);
    Label *label = layout_get_label(ctrl->status_layout, 1, 0);

    switch (ctrl->status) {
    case ekWAIT_LOGIN:
        imageview_image(view, (const Image*)LOGIN16_PNG);
        label_text(label, WAIT_LOGIN);
        break;

    case ekIN_LOGIN:
        imageview_image(view, (const Image*)SPIN_GIF);
        label_text(label, IN_LOGIN);
        break;

    case ekERR_LOGIN:
        imageview_image(view, (const Image*)ERROR_PNG);
        switch (ctrl->err) {
        case ekWS_CONNECT:
            label_text(label, ERR_CONNECT);
            break;
        case ekWS_JSON:
            label_text(label, ERR_JSON);
            break;
        case ekWS_ACCESS:
            label_text(label, ERR_ACCESS);
            break;
        case ekWS_OK:
        cassert_default();
        }
        break;

    case ekOK_LOGIN:
        imageview_image(view, (const Image*)OK_PNG);
        label_text(label, OK_LOGIN);
        break;

    cassert_default();
    }
}

/*---------------------------------------------------------------------------*/

void ctrl_run(Ctrl *ctrl)
{
    Button *setting_button;
    PopUp *lang_popup;
    MenuItem *lang_item;
    uint32_t lang_index;
    ctrl->status = ekWAIT_LOGIN;
    setting_button = cell_button(ctrl->setting_cell);
    layout_show_col(ctrl->main_layout, 1, TRUE);
    button_state(setting_button, ekON);
    menuitem_state(ctrl->setting_item, ekON);
    lang_popup = cell_popup(ctrl->lang_cell);
    lang_index = popup_get_selected(lang_popup);
    lang_item = menu_get_item(ctrl->lang_menu, lang_index);
    menuitem_state(lang_item, ekON);
    menuitem_enabled(ctrl->login_item, TRUE);
    menuitem_enabled(ctrl->logout_item, FALSE);
    menuitem_enabled(ctrl->import_item, FALSE);
    menuitem_enabled(ctrl->export_item, FALSE);    
    i_status(ctrl);
    cell_focus(ctrl->user_cell);
    i_update_product(ctrl);
    window_defbutton(ctrl->window, cell_button(ctrl->login_cell));
}

/*---------------------------------------------------------------------------*/

static void i_OnFirst(Ctrl *ctrl, Event *e)
{
    ctrl->selected = 0;
    i_update_product(ctrl);
    unref(e);
}

/*---------------------------------------------------------------------------*/

static void i_OnImport(Ctrl *ctrl, Event *e)
{
    const char_t *type[] = { "dbp" };
    const char_t *file = comwin_open_file(ctrl->window, type, 1, NULL);
    if (file != NULL)
    {
        ferror_t err;
        if (model_import(ctrl->model, file, &err) == TRUE)
            i_update_product(ctrl);
    }
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_import_item(Ctrl *ctrl, MenuItem *item)
{
    ctrl->import_item = item;
    menuitem_OnClick(item, listener(ctrl, i_OnImport, Ctrl));
}

/*---------------------------------------------------------------------------*/

static void i_OnExport(Ctrl *ctrl, Event *e)
{
    const char_t *type[] = { "dbp" };
    const char_t *file = comwin_save_file(ctrl->window, type, 1, NULL);
    if (file != NULL)
    {
        ferror_t err;
        model_export(ctrl->model, file, &err);
    }
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_export_item(Ctrl *ctrl, MenuItem *item)
{
    ctrl->export_item = item;
    menuitem_OnClick(item, listener(ctrl, i_OnExport, Ctrl));
}

/*---------------------------------------------------------------------------*/

static void i_OnImgDraw(Ctrl *ctrl, Event *e)
{
    const EvDraw *params = event_params(e, EvDraw);
    const Image *image = gui_image(EDIT_PNG);
    uint32_t w, h;
    image_size(image, &w, &h);
    draw_image(params->ctx, image, params->width - w - 10, params->height - h - 10);
    unref(ctrl);
}

/*---------------------------------------------------------------------------*/

static void i_OnImgClick(Ctrl *ctrl, Event *e)
{
    const char_t *type[] = { "png", "jpg" };
    const char_t *file = comwin_open_file(ctrl->window, type, 2, NULL);
    if (file != NULL)
    {
        Image *image = image_from_file(file, NULL);
        if (image != NULL)
        {
            ImageView *view = cell_imageview(ctrl->image_cell);
            imageview_image(view, image);
            image_destroy(&image);
        }
    }
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_image_cell(Ctrl *ctrl, Cell *cell)
{
    ImageView *view = cell_imageview(cell);
    model_image(cell);
    imageview_OnOverDraw(view, listener(ctrl, i_OnImgDraw, Ctrl));
    imageview_OnClick(view, listener(ctrl, i_OnImgClick, Ctrl));
    ctrl->image_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_first_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnFirst, Ctrl));
    ctrl->first_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_first_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnFirst, Ctrl));
    ctrl->first_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnBack(Ctrl *ctrl, Event *e)
{
    if (ctrl->selected > 0)
    {
        ctrl->selected -= 1;
        i_update_product(ctrl);
    }
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_back_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnBack, Ctrl));
    ctrl->back_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_back_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnBack, Ctrl));
    ctrl->back_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnNext(Ctrl *ctrl, Event *e)
{
    uint32_t total = model_count(ctrl->model);
    if (ctrl->selected < total - 1)
    {
        ctrl->selected += 1;
        i_update_product(ctrl);
    }
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_next_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnNext, Ctrl));
    ctrl->next_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_next_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnNext, Ctrl));
    ctrl->next_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnLast(Ctrl *ctrl, Event *e)
{
    uint32_t total = model_count(ctrl->model);
    if (ctrl->selected < total - 1)
    {
        ctrl->selected = total - 1;
        i_update_product(ctrl);
    }
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_last_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnLast, Ctrl));
    ctrl->last_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_last_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnLast, Ctrl));
    ctrl->last_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnAdd(Ctrl *ctrl, Event *e)
{
    model_add(ctrl->model);
    ctrl->selected = model_count(ctrl->model) - 1;
    i_update_product(ctrl);
    cell_focus(ctrl->code_cell);
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_add_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnAdd, Ctrl));
    ctrl->add_cell = cell;
}

/*---------------------------------------------------------------------------*/

static void i_OnDelete(Ctrl *ctrl, Event *e)
{
    model_delete(ctrl->model, ctrl->selected);
    if (ctrl->selected == model_count(ctrl->model) && ctrl->selected > 0)
        ctrl->selected -= 1;
    i_update_product(ctrl);
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_minus_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnDelete, Ctrl));
    ctrl->minus_cell = cell;
}

/*---------------------------------------------------------------------------*/

static void i_OnFilter(Ctrl *ctrl, Event *e)
{
    const EvText *params = event_params(e, EvText);
    EvTextFilter *result = event_result(e, EvTextFilter);
    Combo *combo = event_sender(e, Combo);
    uint32_t color = color_rgb(255, 0, 0);

    if (unicode_nchars(params->text, ekUTF8) >= 3)
    {
        if (model_filter(ctrl->model, params->text) == TRUE)
        {
            color = UINT32_MAX;
            ctrl->selected = 0;
            i_update_product(ctrl);
        }
    }

    combo_color(combo, color);
    result->apply = FALSE;
}

/*---------------------------------------------------------------------------*/

static void i_OnFilterEnd(Ctrl *ctrl, Event *e)
{
    const EvText *params = event_params(e, EvText);
    Combo *combo = event_sender(e, Combo);

    if (model_filter(ctrl->model, params->text) == TRUE)
        combo_ins_elem(combo, 0, params->text, NULL);
    else
        combo_text(combo, "");

    ctrl->selected = 0;
    i_update_product(ctrl);

    combo_color(combo, UINT32_MAX);
}

/*---------------------------------------------------------------------------*/

void ctrl_filter_cell(Ctrl *ctrl, Cell *cell)
{
    Combo *combo = cell_combo(cell);
    combo_OnFilter(combo, listener(ctrl, i_OnFilter, Ctrl));
    combo_OnChange(combo, listener(ctrl, i_OnFilterEnd, Ctrl));
    ctrl->filter_cell = cell;
}

/*---------------------------------------------------------------------------*/

static void i_OnSlider(Ctrl *ctrl, Event *e)
{
    const EvSlider *params = event_params(e, EvSlider);
    uint32_t total = model_count(ctrl->model);
    uint32_t selected = 0;
    if (total > 0)
        selected = (uint32_t)((real32_t)(total - 1) * params->pos);

    if (selected != ctrl->selected)
    {
        ctrl->selected = selected;
        i_update_product(ctrl);
    }
}

/*---------------------------------------------------------------------------*/

void ctrl_slider_cell(Ctrl *ctrl, Cell *cell)
{
    Slider *slider = cell_slider(cell);
    slider_OnMoved(slider, listener(ctrl, i_OnSlider, Ctrl));
    ctrl->slider_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_counter_cell(Ctrl *ctrl, Cell *cell)
{
    ctrl->counter_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_type_cell(Ctrl *ctrl, Cell *cell)
{
    model_type(cell);
    unref(ctrl);
}

/*---------------------------------------------------------------------------*/

void ctrl_code_cell(Ctrl *ctrl, Cell *cell)
{
    model_code(cell);
    ctrl->code_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_desc_cell(Ctrl *ctrl, Cell *cell)
{
    model_desc(cell);
    ctrl->desc_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_price_cell(Ctrl *ctrl, Cell *cell)
{
    model_price(cell);
    ctrl->price_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_user_cell(Ctrl *ctrl, Cell *cell)
{
    ctrl->user_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_pass_cell(Ctrl *ctrl, Cell *cell)
{
    ctrl->pass_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_login_panel(Ctrl *ctrl, Panel *panel)
{
    ctrl->login_panel = panel;
}

/*---------------------------------------------------------------------------*/

static UJson *i_user_webserv(const char_t *user, const char_t *pass, wserv_t *ret)
{
    Http *http = NULL;
    uint32_t status = 0;
    UJson *ujson = NULL;

    *ret = ekWS_OK;
    if (str_empty_c(user) || str_empty_c(pass))
    {
        *ret = ekWS_ACCESS;
        return ujson;
    }

    http = http_create("serv.nappgui.com", 80, NULL);
    if (http == NULL)
    {
        *ret = ekWS_CONNECT;
        return ujson;
    }

    http_add_param(http, "user", user);
    http_add_param(http, "pass", pass);
    http_get(http, "/duser.php", NULL, 0, NULL);
    status = http_response_status(http);
    if (status >= 200 && status <= 299)
    {
        Stream *stm = stm_memory(4096);
        http_response_body(http, stm, NULL);
        ujson = json_read(stm, NULL, UJson);

        if (!ujson)
        {
            *ret = ekWS_JSON;
        }
        else if (ujson->code != 0)
        {
            json_destroy(&ujson, UJson);
            *ret = ekWS_ACCESS;
        }

        stm_close(&stm);
    }
    else
    {
        *ret = ekWS_ACCESS;
    }

    http_destroy(&http);
    return ujson;
}

/*---------------------------------------------------------------------------*/

static uint32_t i_login_begin(Ctrl *ctrl)
{
    Edit *user = cell_edit(ctrl->user_cell);
    Edit *pass = cell_edit(ctrl->pass_cell);
    wserv_t ret = ekWS_OK;
    ctrl->ujson = i_user_webserv(edit_get_text(user), edit_get_text(pass), &ret);
    if (ctrl->ujson != NULL)
    {
        ret = model_webserv(ctrl->model);
        if (ret != ekWS_OK)
            json_destroy(&ctrl->ujson, UJson);
    }

    return (uint32_t)ret;
}

/*---------------------------------------------------------------------------*/

static void i_login_end(Ctrl *ctrl, const uint32_t rvalue)
{
    wserv_t ret = (wserv_t)rvalue;
    if (ret == ekWS_OK)
    {
        Layout *layout = panel_get_layout(ctrl->login_panel, 1);
        ImageView *view = layout_get_imageview(layout, 0, 0);
        Label *label0 = layout_get_label(layout, 0, 1);
        Label *label1 = layout_get_label(layout, 0, 2);
        window_defbutton(ctrl->window, NULL);
        imageview_image(view, ctrl->ujson->data.image64);
        label_text(label0, tc(ctrl->ujson->data.name));
        label_text(label1, tc(ctrl->ujson->data.mail));
        menuitem_enabled(ctrl->login_item, FALSE);
        menuitem_enabled(ctrl->logout_item, TRUE);
        menuitem_enabled(ctrl->import_item, TRUE);
        menuitem_enabled(ctrl->export_item, TRUE);
        panel_visible_layout(ctrl->login_panel, 1);
        ctrl->status = ekOK_LOGIN;
        ctrl->selected = 0;
        i_update_product(ctrl);
        json_destroy(&ctrl->ujson, UJson);
        cell_focus(ctrl->code_cell);
        panel_update(ctrl->login_panel);
    }
    else
    {
        cassert(ctrl->ujson == NULL);
        ctrl->status = ekERR_LOGIN;
        ctrl->err = ret;
    }

    i_status(ctrl);
}

/*---------------------------------------------------------------------------*/

static void i_OnLogin(Ctrl *ctrl, Event *e)
{
    if (ctrl->status != ekIN_LOGIN)
    {
        ctrl->status = ekIN_LOGIN;
        i_status(ctrl);
        osapp_task(ctrl, 0, i_login_begin, NULL, i_login_end, Ctrl);
    }
    
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_login_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnLogin, Ctrl));
    ctrl->login_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_login_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnLogin, Ctrl));
    ctrl->login_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnLogout(Ctrl *ctrl, Event *e)
{
    Edit *edit0 = cell_edit(ctrl->user_cell);
    Edit *edit1 = cell_edit(ctrl->pass_cell);
    model_clear(ctrl->model);
    edit_text(edit0, "");
    edit_text(edit1, "");
    menuitem_enabled(ctrl->login_item, TRUE);
    menuitem_enabled(ctrl->logout_item, FALSE);
    menuitem_enabled(ctrl->import_item, FALSE);
    menuitem_enabled(ctrl->export_item, FALSE);
    ctrl->status = ekWAIT_LOGIN;
    panel_visible_layout(ctrl->login_panel, 0);
    i_update_product(ctrl);
    i_status(ctrl);
    cell_focus(ctrl->user_cell);
    panel_update(ctrl->login_panel);
    window_defbutton(ctrl->window, cell_button(ctrl->login_cell));
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_logout_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnLogout, Ctrl));
    ctrl->logout_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_logout_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnLogout, Ctrl));
    ctrl->logout_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnSetting(Ctrl *ctrl, Event *e)
{
    state_t state = ekON;
    if (event_type(e) == ekEVBUTTON)
    {
        const EvButton *params = event_params(e, EvButton);
        state = params->state;
    }
    else
    {
        Button *button = cell_button(ctrl->setting_cell);
        cassert(event_type(e) == ekEVMENU);
        state = button_get_state(button);
        state = state == ekON ? ekOFF : ekON;
        button_state(button, state);
    }

    menuitem_state(ctrl->setting_item, state);
    layout_show_col(ctrl->main_layout, 1, state == ekON ? TRUE : FALSE);
    layout_update(ctrl->main_layout);
}

/*---------------------------------------------------------------------------*/

void ctrl_setting_cell(Ctrl *ctrl, Cell *cell)
{
    Button *button = cell_button(cell);
    button_OnClick(button, listener(ctrl, i_OnSetting, Ctrl));
    ctrl->setting_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_setting_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnSetting, Ctrl));
    ctrl->setting_item = item;
}

/*---------------------------------------------------------------------------*/

static void i_OnStats(Ctrl *ctrl, Event *e)
{
    const EvDraw *params = event_params(e, EvDraw);
    uint32_t i, n = sizeof(ctrl->stats) / sizeof(real32_t);
    real32_t p = 10.f, x = p, y0 = params->height - p;
    real32_t w = (params->width - p * 2) / n;
    real32_t h = params->height - p * 2;
    real32_t avg = 0, pavg;
    char_t tavg[16];
    color_t c[2];
    real32_t stop[2] = {0, 1};
    c[0] = kHOLDER;
    c[1] = kCOLOR_VIEW;

    draw_fill_linear(params->ctx, c,stop, 2, 0, p, 0, params->height - p + 1);

    for (i = 0; i < n; ++i)
    {
        real32_t hr = h * (ctrl->stats[i] / i_MAX_STATS);
        real32_t y = p + h - hr;
        draw_rect(params->ctx, ekFILL, x, y, w - 2, hr);
        avg += ctrl->stats[i];
        x += w;
    }

    avg /= n;
    pavg = h * (avg / i_MAX_STATS);
    pavg = p + h - pavg;
    bstd_sprintf(tavg, sizeof(tavg), "%.2f", avg);
    draw_text_color(params->ctx, kTXTRED);
    draw_line_color(params->ctx, kTXTRED);
    draw_line(params->ctx, p - 2, pavg, params->width - p, pavg);
    draw_line_color(params->ctx, kCOLOR_LABEL);
    draw_line(params->ctx, p - 2, y0 + 2, params->width - p, y0 + 2);
    draw_line(params->ctx, p - 2, y0 + 2, p - 2, p);
    draw_text(params->ctx, tavg, p, pavg);
}

/*---------------------------------------------------------------------------*/

void ctrl_stats_cell(Ctrl *ctrl, Cell *cell)
{
    View *view = cell_view(cell);
    view_OnDraw(view, listener(ctrl, i_OnStats, Ctrl));
    ctrl->stats_cell = cell;
}

/*---------------------------------------------------------------------------*/

static void i_OnLang(Ctrl *ctrl, Event *e)
{
    MenuItem *item = NULL;
    uint32_t lang_id = 0;
    static const char_t *LANGS[] = { "en_US", "es_ES", "pt_PT", "it_IT", "vi_VN", "ru_RU", "ja_JP" };
    if (event_type(e) == ekEVPOPUP)
    {
        const EvButton *params = event_params(e, EvButton);
        item = menu_get_item(ctrl->lang_menu, params->index);
        lang_id = params->index;
    }
    else
    {
        const EvMenu *params = event_params(e, EvMenu);
        PopUp *popup = cell_popup(ctrl->lang_cell);
        cassert(event_type(e) == ekEVMENU);
        popup_selected(popup, params->index);
        item = event_sender(e, MenuItem);
        lang_id = params->index;
    }

    menu_off_items(ctrl->lang_menu);
    menuitem_state(item, ekON);
    gui_language(LANGS[lang_id]);
}

/*---------------------------------------------------------------------------*/

void ctrl_lang_cell(Ctrl *ctrl, Cell *cell)
{
    PopUp *popup = cell_popup(cell);
    popup_OnSelect(popup, listener(ctrl, i_OnLang, Ctrl));
    ctrl->lang_cell = cell;
}

/*---------------------------------------------------------------------------*/

void ctrl_lang_menu(Ctrl *ctrl, Menu *menu)
{
    uint32_t i, n = menu_size(menu);        
    for (i = 0; i < n; ++i)
    {
        MenuItem *item = menu_get_item(menu, i);
        menuitem_OnClick(item, listener(ctrl, i_OnLang, Ctrl));
    }
    ctrl->lang_menu = menu;    
}

/*---------------------------------------------------------------------------*/

static void i_OnExit(Ctrl *ctrl, Event *e)
{
    osapp_finish();
    unref(ctrl);
    unref(e);
}

/*---------------------------------------------------------------------------*/

void ctrl_exit_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnExit, Ctrl));
}

/*---------------------------------------------------------------------------*/

static void i_OnAbout(Ctrl *ctrl, Event *e)
{
    unref(ctrl);
    unref(e);
    osapp_open_url("https://nappgui.com/en/demo/products.html");
}

/*---------------------------------------------------------------------------*/

void ctrl_about_item(Ctrl *ctrl, MenuItem *item)
{
    menuitem_OnClick(item, listener(ctrl, i_OnAbout, Ctrl));
}

/*---------------------------------------------------------------------------*/

void ctrl_window(Ctrl *ctrl, Window *window)
{
    window_OnClose(window, listener(ctrl, i_OnExit, Ctrl));
    ctrl->window = window;
}

/*---------------------------------------------------------------------------*/

void ctrl_theme_images(Ctrl *ctrl)
{
    bool_t dark = color_dark_mode();
    button_image(cell_button(ctrl->first_cell), (const Image*)(dark ? FIRSTD_PNG : FIRST_PNG));
    button_image(cell_button(ctrl->back_cell), (const Image*)(dark ? BACKD_PNG : BACK_PNG));
    button_image(cell_button(ctrl->next_cell), (const Image*)(dark ? NEXTD_PNG : NEXT_PNG));
    button_image(cell_button(ctrl->last_cell), (const Image*)(dark ? LASTD_PNG : LAST_PNG));
    button_image(cell_button(ctrl->add_cell), (const Image*)ADD_PNG);
    button_image(cell_button(ctrl->minus_cell), (const Image*)MINUS_PNG);
    button_image(cell_button(ctrl->setting_cell), (const Image*)SETTINGS_PNG);
    button_image(cell_button(ctrl->login_cell), (const Image*)LOGIN16_PNG);
    button_image(cell_button(ctrl->logout_cell), (const Image*)(dark ? LOGOUT16D_PNG : LOGOUT16_PNG));
    menuitem_image(ctrl->import_item, (const Image*)OPEN_PNG);
    menuitem_image(ctrl->export_item, (const Image*)(dark ? SAVED_PNG : SAVE_PNG));
    menuitem_image(ctrl->first_item, (const Image*)(dark ? FIRST16D_PNG : FIRST16_PNG));
    menuitem_image(ctrl->back_item, (const Image*)(dark ? BACK16D_PNG : BACK16_PNG));
    menuitem_image(ctrl->next_item, (const Image*)(dark ? NEXT16D_PNG : NEXT16_PNG));
    menuitem_image(ctrl->last_item, (const Image*)(dark ? LAST16D_PNG : LAST16_PNG));
    menuitem_image(ctrl->login_item, (const Image*)LOGIN16_PNG);
    menuitem_image(ctrl->logout_item, (const Image*)(dark ? LOGOUT16D_PNG : LOGOUT16_PNG));
}
❮ Back
Next ❯