[C++] PX, Un toolkit X11 sous Linux

PX, Un toolkit X11 sous Linux [C++] - C++ - Programmation

Marsh Posté le 10-09-2013 à 08:50:21    

Bonjour,

 

J'ai commencé a trifouiller du code X11 en C vers 1995-1996
a une époque ou n'existait que Motif voir Lesstif comme toolkit
pas de GTK, encore moins de Qt
Mais seul, je n'ai travaillé que par moment sur ce toolkit
ce qui explique son état d'avancement ...

 

Ce toolkit X11 est basé sur une approche simpliste
Il n'y a pas d’algorithme sophistiqué
J'utilise assez largement le polymorphisme
et c'est sans doute le concept le plus avancé du C++ dont je fait usage.

 

Malgré la simplicité du code,
le toolkit semble relativement optimisé en terme d'affichage
au regard des fonctionnalités qui ont étés implémentées jusqu’à aujourd'hui.

 

Comme je n'avais pas d'idée de look a donner a ce toolkit j'ai utilisé plus ou moins celui du premier GTK
concernant la partie "feel" du look-n-feel, la partie ergonomie, pour l'instant je n'ai pas définit vraiment de règles.


Message édité par Caffrey's le 02-05-2014 à 03:24:40
Reply

Marsh Posté le 10-09-2013 à 08:50:21   

Reply

Marsh Posté le 10-09-2013 à 08:50:34    

Explication du code

 

1. libPX.cc: la boucle des évènements

 
Code :
  1. unsigned char PXObj::MainLoop()
  2. {
  3. XEvent        xevent;
  4.     active_widget = NULL;
  5.     event_widget = NULL;
  6.     // Display initial windows
  7.     list<PXWindow *>::iterator iwin = init_windows.begin();
  8.     while (iwin != init_windows.end())
  9.     {
  10.         (*iwin)->InitMap(0);
  11.         iwin++;
  12.     }
  13.     // The main loop
  14.     in_main_loop = true;
  15.     exit_main_loop = 0;
  16.     while (exit_main_loop == 0)
  17.     {
  18.         XNextEvent(xdisplay, &xevent);
  19.         event_widget = FindWidgetByEventXWindow(xevent.xany.window); // recherche dans la liste des widgets
  20.         if ( event_widget != NULL )
  21.         {
  22.             if (xevent.type == Expose)
  23.                 if (xevent.xexpose.count > 0)
  24.                     continue;
  25.          
  26.             if (active_widget != NULL && xevent.type == ButtonRelease) // pour éviter l'usage du GrabPointer
  27.             {   
  28.                 active_widget->HandleEvent(&xevent);
  29.             }
  30.             else if (focus_widget != NULL && (xevent.type == KeyPress ||  xevent.type == KeyRelease)) // pour éviter l'usage du GrabKeyboard
  31.             {
  32.                 focus_widget->HandleEvent(&xevent);
  33.             }
  34.             else if (event_widget != NULL)       
  35.             {
  36.                 event_widget->HandleEvent(&xevent);
  37.             }       
  38.         }
  39.     }
  40.     in_main_loop = false;
  41.    
  42.     // Unmap and delete windows
  43.     list<PXWindow *>::iterator win = init_windows.begin();   
  44.     while (win != init_windows.end())
  45.     {
  46.         (*win)->Unmap();
  47.         delete (*win);
  48.         win++;
  49.     }
  50.     return exit_main_loop;
  51. }


2. px_widget.cc: la fonction de dispatch des évènements

 
Code :
  1. void PXWidget::HandleEvent(XEvent *xevent)
  2. {
  3.     switch (xevent->type)
  4.     {
  5.         case KeyPress:
  6.             OnXKeyPress(xevent);
  7.             break;
  8.         case KeyRelease:
  9.             OnXKeyRelease(xevent);           
  10.             break;
  11.         case MappingNotify:
  12.             OnXMappingNotify(xevent);
  13.             break;
  14.         case ButtonPress:
  15.             OnXButtonPress(xevent);
  16.             break;
  17.        
  18.         case ButtonRelease:
  19.             OnXButtonRelease(xevent);
  20.             break;
  21.            
  22.         case EnterNotify:
  23.             OnXEnterNotify(xevent);
  24.             break;
  25.         case LeaveNotify:
  26.             OnXLeaveNotify(xevent);
  27.             break;
  28.         case MotionNotify:
  29.             OnXMotionNotify(xevent);           
  30.             break;
  31.         case Expose:
  32.             OnXExpose(xevent);
  33.             break;
  34.      
  35.         case ConfigureNotify:
  36.             OnXConfigureNotify(xevent);
  37.             break;
  38.         case ClientMessage:
  39.             OnXClientMessage[/#0000ff](xevent);
  40.             break;
  41.     // Le reste
  42.         default:
  43.             ;
  44.     }
  45. }


3. px_*.cc: les différentes fonctions de gestion des évènements


Message édité par Caffrey's le 15-09-2013 à 07:34:38
Reply

Marsh Posté le 10-09-2013 à 08:50:43    

Hiérarchie des classes de widgets

 

   http://img203.imageshack.us/img203/7343/xssk.jpg

 

En bleu ciel, les classes abstraites
En bleu roi, les classes + ou - implémentées
En orange, les classes non implémentée mais qui possèdent déjà un fichier *.cc


Message édité par Caffrey's le 10-09-2013 à 11:40:19
Reply

Marsh Posté le 10-09-2013 à 09:17:21    

Ceux qui voudront l'utiliser pour eux,
pourrons le faire dans le cadre d'une licence OpenSource (non encore définie)

 

J'aimerais surtout que des personnes me rapporte des remarques
sur des choses que j'ai clairement mal implémentées
ou que j'aurais pu implémenter plus simplement.

 

Ce qui m'intéresserais aussi et surtout c'est que vous me disiez
les fonctionnalités que vous voudriez voir dans un toolkit

 

- Look-n-feel "Blender" ?

 

- Composant Editeur de texte avancé ?
   - avec folding/repli des blocs des codes
   - colorisation syntaxique

 

- autre chose ...

 

Et la base ...

 

- Lissage des polices (non implémenté)

 

Bientôt un lien vers les sources ...


Message édité par Caffrey's le 10-09-2013 à 15:11:14
Reply

Marsh Posté le 10-09-2013 à 16:36:15    

Oué, les toolkits d'interface ... un domaine dans lequel je travaille plus ou moins directement depuis une 15aine d'années. Autant dire que j'ai eu le temps de voir un peu près tout ce qui se faisait sous le soleil: AmigaOS, X11/Xt/Xm (motif), un peu de Java/AWT et Java/Swing, Win32, MFC, wxWidgets, un peu de GTK, un peu de QT, évidemment HTML/JS et .... mon propre toolkit (SIT: simple interface toolkit, basé sur Win32).
 
Avant de faire des remarques sur le peu que tu as montré, la moindre des choses que tu devrais faire, c'est:
* Un exemple de code "Hello World": une fenêtre, un label "Hello World" + boucle d'événement minimale. Histoire d'avoir une idée de quoi ressemble l'API.
* Un screenshot avec quelques widgets un peu plus avancés + le code source.
 
 
Comme je te l'ai dit, j'ai touché à pas mal d'API dans ce domaine et il y a un piège qu'il est extrêmement facile de tomber quand on fait une API d'interface: over-engineering, ou ce que j'aime bien appeler le fardeau cognitif. Le but de toute API est à mon sens est de minimiser ce fardeau, parce qu'il y a des limites à ce que le cerveau peut emmagasiner et on a bien souvent autre chose à foutre que de comprendre l'esprit parfois tordu qui a conduit à certains choix d'API. Dans ce domaine, il y a quelques classiques qui ont foiré cet aspect:
 
* Win32: je devrais plutôt dire la partie "comctl" (common control) et dans une moindre mesure GDI, parce que le reste est assez banal (mais très stable). Bourré de piège à con, exemple MSDN ultra merdique, documentation pas toujours très claire, API mamouthesque, ultra bas niveau: faut pas s'étonner si une génération entière de programmeur ont maltraité cette API et que Microsoft se retrouve avec toute une clique d'application mal foutue, qui bride toute évolution: quand on a fardeau cognitif aussi élevé, faut pas espérer que tout le monde fasse l'effort de bien maitriser l'API.
 
* GTK: l'API est assez propre, mais la productivité de ce toolkit est merdicimale. C'est la raison de pourquoi je n'ai pas cherché à m'investir plus dans ce truc. L'API est composé de plusieurs milliers de fonctions au nom abominablement longs et extrêment chiant à s'en souvenir, même avec de l'auto-complétition. Pour un peu, j'avais l'impression de coder en J2EE.
 
* MFC/wxWidgets: codé à une époque ou le C++ était du C avec des classes, le résultat est que tu te prends dans la figure le fardeau cognitif du C++ mal employé avec le fardeau de l'API. Ça à le mérite de fonctionner, mais absolument pas plaisant à utiliser.
 
À l'opposé ceux qui ont plutôt bien conçu :
* HTML/JS: pas sûr de faire l'unanimité, mais de mon expérience (8ans) avec l'API DOM + HTML, le fardeau cognitif est relativement faible. L'API est un peu verbeuse (document.createElement(...), document.getElementById(), ...), mais c'est très facile à changer. Le choix des widgets est un peu limité, mais on a toute la liberté du HTML pour combler les lacunes.
 
* Xm/SIT: dans le contexte de l'époque, c'était pas mal (à l'heure actuelle, ça a plutôt mal vieillit: j'avais revu certains morceaux de code que j'avais écris avec Xm, il y a plus de 10ans, bah, c'était pas super clair). Le noyau dur de l'API était composé de 4 ou 5 fonctions qui te permettais de coder 99% de l'application: la surcharge cognitive était extrêmement faible. Mais il y avait pas mal de trucs moisi: gestion des chaines de caractères (XmStringCreate(), XmStringCreateLocalized(), ...), API verbeuse et limite chiante à regarder (une suite sans fin de XtCreateManagedWidget()), et un choix de widget limité, pour ne pas dire désuet même pour l'époque (XmNoteBook, XmList), licence de merde, etc... SIT (mon toolkit, donc) utilise une API similaire à Xm, mais j'ai viré tout ce que je considérais moisi (chaine de caractère, verbosité, choix de widget pas terrible, ...). Je pense avoir fait un bon boulot, mais ça n'est pas aussi simple qu'HTML/JS. Un exemple de hello world avec SIT:
 

Code :
  1. #include <SIT.h>
  2.  
  3. static int close(SIT_Widget w, APTR cd, APTR ud)
  4. {
  5.     SIT_CloseDialog(w);
  6.     return 1;
  7. }
  8.  
  9. int my_main(int nb, char * argv[])
  10. {
  11.     SIT_Widget app    = SIT_CreateWidget("HelloWorld", SIT_APP, NULL, NULL);
  12.     SIT_Widget dialog = SIT_CreateWidget("Hello", SIT_DIALOG, app,
  13.         SIT_Margins, 10, 10, 10, 10,
  14.         NULL
  15.     );
  16.     SIT_CreateWidgets(dialog,
  17.         "<label name=label title='hello, world' font=System/300%>"
  18.         "<button name=ok title=Bye right=FORM top=WIDGET,label,5 margins=2,8,2,8>"
  19.     );
  20.     SIT_AddCallback(SIT_GetById(dialog, "ok" ), SITE_OnActivate, close, NULL);
  21.     SIT_ManageWidget(dialog);
  22.  
  23.     return SIT_Main();
  24. }


 
Ça donne:  
http://i.imgur.com/2xWuJGr.png


Message édité par tpierron le 10-09-2013 à 16:37:30
Reply

Marsh Posté le 11-09-2013 à 11:14:01    

Pour l'instant mon hello world s'écrit comme ça

 
Code :
  1. #include <libPX.hh>
  2. #include <px_window.hh>
  3. #include <px_label.hh>
  4. #include <px_button.hh>
  5. int main(int argc, char **argv)
  6. {
  7. PXObj        *px;
  8. PXWindow     *win;
  9.     px = new PXObj();
  10.     win = new PXWindow(px, px->GetRootWindow(), "FENETRE1", "Hello", pxAlignNone, 10, 10, 150, 75, PXAnchors(false, false, false, false), NULL, NULL, pxSunk);
  11.    
  12.     px->SetInitialWindow(win);
  13.     new PXLabel(px, win, "LABEL1", " Hello World ! ", pxAlignTop);
  14.     new PXButton(px, win, "BUTTON1", " Bye ", pxAlignClient);
  15.     px->Main();
  16. }


et ressemble à ceci

 

http://imageshack.us/a/img28/6093/36zx.png

 

Bon, il n'y a pas encore de gestion des fontes et la position du texte dans le label
En fait j'ai programmé le composant label hier rapidement, histoire de montrer quelque chose.

 

Mais en fait je vais voir si je peux pas transformer les new PXWindow() par des méthodes de classe PXWindow::Create()

 

Sinon le PXObj .. (que je vais renommer en PXApplication) doit s'occuper de tous les écrans d'un display (voir faire du multi-displays)

 

J'ai pas encore programmé les Callbacks mais je pense les écrire comme ça widget->AssignCallback(pxOnClick, proc)

 

Sinon, il est pas mal ton toolkit, c'est assez différent du mien, il a l'air sophistiqué.
tu as pensé a le porter en C++ ?

 

J'attends tes remarques ...


Message édité par Caffrey's le 11-09-2013 à 18:23:43
Reply

Marsh Posté le 11-09-2013 à 12:26:12    

un exemple d'interface plus complète

 
Code :
  1. extern "C" {
  2. #include <libP.h>
  3. }
  4. #include <libPX.hh>
  5. #include <px_window.hh>
  6. #include <px_button.hh>
  7. #include <px_arrowbutton.hh>
  8. #include <px_popupmenu.hh>
  9. #include <px_pulldownmenu.hh>
  10. #include <px_textwidget.hh>
  11. #include <px_textfield.hh>
  12. #include <px_texteditor.hh>
  13. #include <px_slider.hh>
  14. #include <px_tabcontrol.hh>
  15. PXObj   *px;
  16. PXWindow   *win;
  17. PXPopupMenu *menu;
  18. PXButton  *button_test1;
  19. PXMenuWidget *m_wm, *m_session, *m_quitter, *m_oui;
  20. PXPulldownMenu *m_bar;
  21. PXMenuWidget *m_file, *m_quitter2, *m_oui2, *m_edit, *m_search, *m_help;
  22. PXButton  **b;
  23. PXTextField  *field1;
  24. PXTextEditor *editor1;
  25. PXTabControl *tcontrol;
  26. PXTabSheet **tsheets;
  27. int main(int argc, char **argv)
  28. {
  29. px = new PXObj("default" );
  30. win = new PXWindow(px, px->GetRootWindow(), argv[0], argv[0], pxAlignNone, 348, 72, 700, 500, PXAnchors(false,false,false,false), NULL, NULL, pxSunk) ;
  31. px->SetInitialWindow(win);
  32. // Le popup-menu
  33. menu = new PXPopupMenu(px, px->GetRootWindow(), "POPMENU" );
  34. menu->AddItem("MENU" );
  35. menu->AddSeparator();
  36. menu->AddItem("xterm" );
  37. menu->AddItem("xed" );
  38. menu->AddItem("xv" );
  39. menu->AddItem("gimp" );
  40. menu->AddItem("editres" );
  41. menu->AddItem("netscape" );
  42. menu->AddSeparator();
  43. m_session = menu->AddItem("Session" )->AddSubMenu();
  44.  m_session->AddItem("Rafraichir" );
  45.  m_quitter = m_session->AddItem("Quitter" )->AddSubMenu();
  46.   m_quitter->AddItem("Non" );
  47.   m_oui = m_quitter->AddItem("Oui" )->AddSubMenu();
  48.    m_oui->AddItem("J'hesite encore" );
  49.    m_oui->AddItem("C'est parti" );
  50. win->SetPopupMenu(menu, Button1);
  51.  // La barre de menu
  52. m_bar = new PXPulldownMenu(px, win, "PULLDOWNMENU" );
  53. m_file = m_bar->AddItem("Fichier" )->AddSubMenu();
  54. m_file->AddItem("Nouveau ..." );
  55. m_file->AddItem("Ouvrir ..." );
  56. m_file->AddItem("Enregistrer" );
  57. m_file->AddItem("Enregistrer sous ..." );
  58. m_file->AddSeparator();
  59. m_file->AddItem("Imprimer" );
  60. m_file->AddSeparator();
  61.  m_quitter2 = m_file->AddItem("Quitter" )->AddSubMenu();
  62. m_edit = m_bar->AddItem("Edition" )->AddSubMenu() ;
  63.  m_edit->AddItem("Annuler" );
  64.  m_edit->AddItem("Rétablir" );
  65.  m_edit->AddSeparator();
  66.  m_edit->AddItem("Copier" );
  67.  m_edit->AddItem("Couper" );
  68.  m_edit->AddItem("Coller" );
  69.  m_edit->AddItem("Supprimer" );
  70. m_search = m_bar->AddItem("Rechercher" )->AddSubMenu();
  71.  m_search->AddItem("Rechercher" );
  72.  m_search->AddItem("Rechercher le suivant" );
  73.  m_search->AddItem("Rechercher le précédent" );
  74.  m_search->AddSeparator();
  75.  m_search->AddItem("Remplacer" );
  76.  m_search->AddSeparator();
  77.  m_search->AddItem("Aller à la ligne" );
  78. m_bar->AddSeparator();
  79. m_help = m_bar->AddItem("Aide" )->AddSubMenu();
  80.  m_help->AddItem("Sommaire" );
  81.  m_help->AddSeparator();
  82.  m_help->AddItem("A Propos" );
  83. // Les boutons
  84. b =  new PXButton*[7];
  85. b[0] = new PXButton(px, win, "", "   B 1   ", pxAlignNone, 6,        2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,true,false));
  86. b[1] = new PXButton(px, win, "", "   B 2   ", pxAlignNone, b[0]->GetX()+b[0]->GetWidth(), 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,true,false));
  87. b[2] = new PXButton(px, win, "", "   B 3   ", pxAlignNone, b[1]->GetX()+b[1]->GetWidth(), 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,true,false));
  88. b[3] = new PXButton(px, win, "", "   B 4   ", pxAlignNone, b[2]->GetX()+b[2]->GetWidth(), 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,true,false));
  89. b[4] = new PXButton(px, win, "", "   B 5   ", pxAlignNone, b[3]->GetX()+b[3]->GetWidth(), 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,true,false));
  90. b[5] = new PXButton(px, win, "", "   B 6   ", pxAlignNone, b[4]->GetX()+b[4]->GetWidth(), 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,false,true));
  91. b[6] = new PXButton(px, win, "", "   B 7   ", pxAlignNone, b[5]->GetX()+b[5]->GetWidth(), 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,false,true));
  92. field1 = new PXTextField(px, win, "Champ 1", pxAlignNone, 2, 64, 700-4, 0, PXAnchors(true,false,true,true)) ;
  93. try
  94. {
  95.  tcontrol = new PXTabControl(px, win, "TABCONTROL1", pxAlignNone, 2, 100, 700-4, 200-2, PXAnchors(true,true,true,true), pxTabsPosTop);
  96.  tsheets =  new PXTabSheet*[4];
  97.  for (unsigned char ts=1; ts<=4; ts++)
  98.   tsheets[ts-1] = tcontrol->AddTabSheet(string("fichier" )+char('1'+ts-1)+".cpp" );
  99. }
  100. catch (ePXWidgetTooSmall &e)
  101. {
  102.  cfprintf(stderr, "new PXTabControl(): Exception ePXWidgetTooSmall: %s !\n", e.what());
  103. }
  104. try
  105. {
  106.  editor1 = new PXTextEditor(px, tsheets[1]->GetSheet(), "EDITEUR1", pxAlignTop, 0, 0, 0, 200, PXAnchors(true,false,true,true));
  107. }
  108. catch (ePXWidgetTooSmall &e)
  109. {
  110.  cfprintf(stderr, "new PXTextEditor(): Exception ePXWidgetTooSmall: %s !\n", e.what());
  111. }
  112. px->Main();
  113. return 1;
  114. }
 

http://imageshack.us/a/img534/6412/693l.png

 


Message édité par Caffrey's le 11-09-2013 à 18:26:10
Reply

Marsh Posté le 11-09-2013 à 19:05:18    


Mouais, l'avantage d'avoir utilisé une tétrachiée d'API, c'est qu'on eu le temps de séparer ce qui est utile, de ce qui est de la masturbation intellectuelle. Dans tous les cas, je trouve que l'approche objet pure (héritage, polymorphisme, ...) pour ce genre d'API, n'apporte pas grand chose, et dans le cas du C++, je serais tenté de dire que le fardeau cognitif du langage apporte plus de problèmes que ça n'en résouds (note: je parle de la façade, en interne il y aura forcément une approche objet).
 
Une application de bureau finalisée, ça demande pas mal d'effort et de souci du détail (plus qu'une appli web, je trouve): tu n'as en général franchement pas envie de te battre contre l'API et/ou le langage. Il y a un équilibre pas évident à trouver: permettre une certaine liberté d'expression, sans imposer une surcharge cognitive démentielle. Dans le cas de SIT, j'ai poussé cette logique aussi loin que possible, et à force de virer des trucs inutiles, c'est là qu'on se rends compte que la plupart des concepts du C++ deviennent inutiles aussi.
 
Par exemple: la gestion de tes menus. Tu as utilisé une approche objet/hiérarchique classique. 7 variables déclarées, pas mal de surcharge liée à l'API (menu->AddItem, menu->AddSeparator, ...): la partie des données propre au menu est noyée dans les appels de méthodes et même à ce prix tu as juste créé le texte et la hiérarchie du menu, rien d'autre. Compare ça, avec une table de structure tout ce qui a de plus basique (inspiré d'AmigaOS):
 

Code :
  1. static SIT_MenuStruct menutest[] =
  2. {
  3.     {1, "&Fichier"},
  4.         {2, "&Nouveau...",          "N",  0, 101},
  5.         {2, "&Ouvrir",              "O",  0, 102},
  6.         {2, "&Enregistrer",         "S",  0, 103},
  7.         {2, "Enregistrer &sous...", NULL, 0, 104, SITK_FlagCtrl + SITK_FlagShift + 'S'},
  8.         {2, SITM_SEPARATOR},
  9.         {2, "&Imprimer",            "O",  0, 105},
  10.         {2, SITM_SEPARATOR},
  11.         {2, "Quitter",              "Q",  0, 106},
  12.     {1, "&Edition"},
  13.         {2, "&Annuler",             "Z",  0, 201},
  14.         {2, "&Retablir",            NULL, 0, 202, SITK_FlagCtrl + SITK_FlagShift + 'Z'},
  15.         {2, SITM_SEPARATOR},
  16.         {2, "Couper",               "X",  0, 203},
  17.         {2, "&Copier",              "C",  0, 204},
  18.         {2, "&Coller",              "V",  0, 205},
  19.         {2, "&Supprimer",           NULL, 0, 206},
  20.     {1, "&Rechercher"},
  21.         {2, "&Rechercher",              "F",  0, 301},
  22.         {2, "&Rechercher le suivant",   NULL, 0, 302, SITK_F3},
  23.         {2, "&Rechercher le precedant", NULL, 0, 303, SITK_FlagShift + SITK_F3},
  24.         {2, SITM_SEPARATOR},
  25.         {2, "&Remplacer",               "R",  0, 304},
  26.         {2, SITM_SEPARATOR},
  27.         {2, "&Aller a la ligne",        "G",  0, 304},
  28.     {1, "&Aide"},
  29.         {2, "Sommaire",             "F1", 0, 401},
  30.         {2, SITM_SEPARATOR},
  31.         {2, "&A propos",            "?", 0, 402},
  32.     {0}
  33. };


 
Et tu sais quoi? Ça couvre tous les cas de figure que j'ai eu à gérer jusqu'à présent et ça simplifie un paquet de trucs:

  • Une seule structure de données à connaitre (SIT_MenuStruct). Une seule variable à déclarer/manipuler. Des concepts ultra-basiques: table, structure. Ton cerveau pourra se concentrer sur autre chose.
  • La surcharge liée à l' "API" est quasi nulle (2 caractères: {}), les données propres au menu sont exposées clairement. Tu dois probablement pouvoir t'en sortir en C++ avec des templates, mais le résultat en vaut-il la peine? J'en doute.
  • La fonction qui converti une table de SIT_MenuStruct en win32 fait un peu près 100 lignes de code et pas besoin d'avoir 15ans de métier pour la comprendre.


On peut faire la même remarque pour la création de tes boutons:

Code :
  1. b[0] = new PXButton(px, win, "", "   B 1   ", pxAlignNone, 6, 2+m_bar->GetHeight()+6, 0, 0, PXAnchors(true,false,true,false));


Vu comme ça, sans connaître les paramètres de ton constructeur, ce code n'est pas spécialement clair. D'autant que ça va être la merde si tu veux faire évoluer ça:

  • L'ordre des paramètres sont fixes, il faut s'en souvenir pour chaque constructeurs (qui ne doivent pas varier des masses j'imagine, mais tout de même).
  • Quid d'un bouton avec icone? Nouvelle classe? Nouveau constructeur avec encore plus de paramètres?
  • Comment faire pour modifier ces paramètres après création ? Encore une autre interface/API ?
  • Quid d'un modèle de placement des contrôles plus évolués (parce que là, tu as l'air de calculer plus ou moins manuellement) ? Nouvelle interface ?


 
Tu sais comment simplifier ça ? une interface vararg en C, tout ce qu'il y a de plus basique. C'est l'approche utilisé par Xt et donc Motif et dans une moindre mesure AmigaOS. Hop, ça simplifie:

  • Pas besoin de ce soucier de l'ordre des arguments (où plutôt: les vararg ont tellement peu d'info transmis que tu dois sortir l'artillerie lourde pour gérer ça correctement).
  • On peut rajouter des attributs sans modifier quoi que ce soit à l'API/ABI.
  • Si on n'est pas trop nul, on peut se démerder pour que l'interface de création, modification et récupération des attributs utilise le même principe (vararg donc). Sur AmigaOS, j'avais le souvenir que seule l'interface de création utilisait ça.
  • Le modèle objet devient inutile (du moins la partie en façade, en interne il y aura forcément une spécialisation, mais ça, l'utilisateur s'en fout): tout ce que l'utilisateur a besoin de voir, c'est un objet opaque, générique (widget) et une liste d'attribut que cet objet accepte. Si tu te démerdes bien tu peux même ignorer les attributs non supportés, encore un truc de moins à connaître.


Cela dit, le défaut d'une interface vararg, c'est qu'il soit préférable que l'attribut encode la taille de l'argument (je ne l'ai pas fait dans SIT, je pense que c'est une erreur) et prier pour l'argument soit effectivement de cette taille. Avec une API 32bits, ça passe plus souvent que ça ne casse parce qu'un paquet de truc fait 4 octets (char [promotion implicite], short, int, long, void *, il y a juste long long et double qui font 8octets). En 64bits, c'est un peu plus casse gueule. Dans SIT, j'ai un peu mitigé le problème en passant par des macros (certains attributs seulement): la macro définit l'attribut et converti (cast) l'argument avec le bon type (en général quand l'argument est de type int64). Rien n'est parfait, mais je pense que c'est un bon compromis entre simplicité, surcharge cognitive pour l'utilisateur et surcharge au niveau du code pour gérer tout ça (notamment la préservation de l'ABI est un plus non négligeable).
 
Par exemple pour créer ton interface, en SIT, ça donne :
 

Code :
  1. #include <SIT.h>
  2. int my_main(int nb, char * argv[])
  3. {
  4.     SIT_Widget app = SIT_CreateWidget("UnitTest", SIT_APP, NULL, NULL);
  5.     SIT_Widget dialog = SIT_CreateWidget("TestWidgets", SIT_DIALOG, app,
  6.         SIT_Title, "Test",
  7.         SIT_Menu,  menutest,
  8.         NULL
  9.     );
  10.  
  11.     SIT_CreateWidgets(dialog,
  12.         "<button name=b1 title='B 1' left=FORM,0,5  margins=0,8,0,8>"
  13.         "<button name=b2 title='B 2' left=WIDGET,b1 maxwidth=b1>"
  14.         "<button name=b3 title='B 3' left=WIDGET,b2 maxwidth=b2>"
  15.         "<button name=b4 title='B 4' left=WIDGET,b3 maxwidth=b3>"
  16.         "<button name=b5 title='B 5' left=WIDGET,b4 maxwidth=b4>"
  17.         "<button name=b6 title='B 6' left=WIDGET,b5 maxwidth=b5>"
  18.         "<button name=b7 title='B 7' left=WIDGET,b6 maxwidth=b6>"
  19.         "<editbox name=edit title='Composant PXTextField' top=WIDGET,b1,5 left=FORM right=FORM>"
  20.         "<tab name=tab top=WIDGET,edit,5 tabStr='fichier1.cpp\tfichier2.cpp\tfichier3.cpp\tfichier4.cpp'"
  21.         " left=FORM right=FORM bottom=FORM>"
  22.         "  <editbox name=editarea width=640 height=480 minWidth=200 editType=", SITV_MultiLineEdit,
  23.         "   title='Composant PXTextField' left=FORM right=FORM top=FORM bottom=FORM/>"
  24.         "</tab>"
  25.     );
  26.     SIT_ManageWidget(dialog);
  27.     return SIT_Main();
  28. }


 
4 fonctions, 1 table de structure (avec les callbacks, il y aurait une fonction de plus + un prototype standardisé pour le callback à connaitre).
 
Note: SIT_CreateWidgets() utilise encore une autre interface: un mix d'HTML et de vararg. Ça a l'avantage d'être extrêmement compact, on voit relativement bien l'imbrication des widgets et les paramètres qui leur sont transmis. Comme son nom l'indique, elle est basée sur SIT_CreateWidget(). Par exemple pour créer le premier bouton, ça donnerait un truc du gerne :
 

Code :
  1. SIT_Widget button = SIT_CreateWidget("b1", SIT_PUSHBUTTON, dialog,
  2.     SIT_Title,   "B 1",
  3.     SIT_Left,    SITV_AttachForm, NULL, 5,
  4.     SIT_Margins, 0, 8, 0, 8,
  5.     NULL
  6. );


Reply

Marsh Posté le 11-09-2013 à 20:47:15    

Juste histoire d'en rajouter encore une couche: ce que j'essaye d'illustrer, c'est ce que j'ai dit au début: toute API se doit de minimiser le fardeau cognitif. J'ai donné mon toolkit en exemple, mais ce n'est pas une parole d'évangile à suivre aveuglément. Si tu arrives à trouver une combinaison de fonctions/structures/classes/templates/incantations vaudoux, qui fait qu'il y a moins de concepts à connaitre, avec une API plus robuste, plus expressive, qui puisse éventuellement s'intégrer à des systèmes existants (mais avec l'API win32, tu vas pleurer), il vaut mieux ne pas copier pas ce que j'ai fait, mais y chercher de l'inspiration.
 
Parce bon, même si je pense avoir fait un bon boulot avec SIT, c'est loin d'être parfait:

  • Espace de nommage. L'instruction "namespace", c'est loin d'être du luxe.
  • C'est un peu trop bas niveau à mon gout (comparé à HTML/JS). C'était bien à la fin des années 90/début 2000, maintenant c'est un peu lourdingue.
  • La gestion des menus est un peu bof, principalement parce que l'API Win32 est une grosse merde. Pareil pour le TabControl: jamais vu une API aussi mal foutue.
  • Le module "graphics.h". Là aussi, Microsoft n'a absolument rien fait pour faciliter la vie: ils se contentent d'empiler les API incompatibles (GDI, GDI+, Direct2D, WPF, ...) et la moitié est en état de délabrement avancé (GDI, GDI+).
  • Tu noteras que je n'ai pas donné de liens vers les sources. Il y a une raison: j'utilise majoritairement ça pour moi même, dans quelques applis commerciales (mais au fil des ans, je migre +/- tout vers HTML/JS). Ma version de travail contient pas mal de bidouille, la flemme de mettre ça au propre.


Message édité par tpierron le 11-09-2013 à 20:54:08
Reply

Marsh Posté le 12-09-2013 à 04:16:27    

Juste une remarque sur ton toolkit
ce qui surtout gêné c'est les ID de widget, et la fonction SIT_GetById(dialog, "ok" )
je suppose que dans une interface importante, on doit en retrouver énormément.

 

Je préfère quand même l'encapsulation objet qui offre sécurité et aussi et surtout un appel simple des méthodes spécifiques widget->MethodeDeLaClasse() (en interne et pour l'API)
Je sais pas comment tu fait ça avec les ID !?

 


Aussi non, c'est clair que la création de mes menus est lourde
je m'en suis aperçu.

 

Je vais voir ce que je peux faire pour le toolkit (pour les menus je vais utiliser une struct)
Peut être opter pour un mélange entre souplesse de la variabilité des arguments,
mais sans avoir a écrire un mini-parser pour lire la structure balisée de la déclaration de l'interface.

 

Peut-être pour des prototypes de fonctions de création comme :

 
Code :
  1. PXButton::PXButton(PXApplication *, PXContainerWidget *, PXArg, PXArg, ...)
 

avec PXArg une chaîne: "attribute_name=value"

 

(Faut que je vois si on peut faire ça en C++ pour les constructeurs)

 

ou encore

 
Code :
  1. pxapp->CreateButton(PXContainerWidget *, PXArg, PXArg, ...)



Message édité par Caffrey's le 12-09-2013 à 17:46:09
Reply

Marsh Posté le 12-09-2013 à 04:16:27   

Reply

Marsh Posté le 12-09-2013 à 20:56:31    


Tu devrais essayer d'approcher ton API à niveau beaucoup plus haut: avant de regarder les spécifités techniques que tel ou tel langage peut apporter, demande toi qu'est ce que tu cherches à apporter à l'utilisateur (de ta DLL, donc du programmeur). À ce niveau c'est trop tôt de ce soucier de l'utilisateur final. Dans le cas de SIT, j'avais ces idées en tête:
 

  • API simple, très facile à approcher (aussi peu de concept que possible) et compacte. GTK par exemple à une API simple, facile à approcher (il n'y a pratiquement que des fonctions), mais absolument pas compacte, je dirais même que GTK est aux interfaces, ce que J2EE est au développement web.
  • Si l'API est simple, la compatibilité ascendante (API et ABI) est facile à assurer.
  • Si l'ABI est simple et L'API compacte, l'interfaçage avec d'autres langages est relativement simple.
  • Si l'API est simple, la documentation est intéressante à écrire. Ça à l'air d'un détail dérisoire, mais comme dit le dicton: ce qui se conçoit bien, s'énonce clairement. Et à fortiori, son corollaire: si on a des problèmes à décrire l'API, c'est soit qu'on est pas doué pour écrire des docs (et donc vaudrait mieux se demander pourquoi chercher à concevoir une API, si on est pas très doué pour l'expliquer) ou que l'API est probablement mal foutue.
  • Si la doc est intéressante à écrire, elle devrait être intéressante à lire et donc pousser le programmeur à s'investir un minimum, à faire les choses dans les règles de l'art. J'aimerais utiliser les win32 en exemple, mais ce n'est pas amusant de taper tout le temps sur les mêmes handicapés, alors regarde GTK: prends la doc d'un GtkButton: une suite sans fin de fonction au nom plus chiant les uns que autres avec des variations infinitésimales entre elles. Je n'ose même pas imaginer l'horreur que ça du être d'écrire cette doc. Indice: si c'est chiant à lire/écrire, c'est que c'est très probablement chiant à utiliser...
  • Un délire personnel: surcharge du code aussi faible que possible. Certains ne sont contents que lorsque le poids des dépendances transforme le logiciel en trou noir, ou que toutes les fonctionnalités imaginables d'un langage soient utilisées. Pas moi. Je sais que la RAM ne vaut pas cher, mais je n'arrivais absolument pas à m'enlever l'idée de faire plus avec moins.


 
Tu as l'air de tenir à l'analyse statique du compilateur. Je ne te cacherais pas qu'après avoir travaillé pendant des années avec les technos web, ce n'est pas un point qui m'a semblé important. Il y a deux écoles: celle qui préfère faire un maximum de vérification à la compilation, quitte à avoir une API verbeuse/complexe, et l'autre qui préfére avoir une API relativement générique, qui fait son possible pour honorer les demandes, quitte à ignorer/corriger les erreurs. Les deux approches se valent, mais là encore après toutes ces années à coder avec les technos web, je préfère la seconde (c'est par exemple aussi l'approche utilisé par sqlite (http://www.sqlite.org/different.html#Manifest%20typing)).
 
Tu vois: avant même d'écrire la moindre ligne code, j'ai déjà fait des choix très structurants: ABI simple = pas de C++ en façade. Là aussi ça aide d'avoir un peu regardé ce qui se fait ailleurs, histoire d'éviter de faire les mêmes erreurs/reprendre les bonnes idées. Tu n'as pas l'air très décidé sur ton architecture globale, alors si je peux te donner un bon conseil, je vais te répéter ce que j'ai dis au début: pense au fardeau cognitif. Chaque classes, fonctions, structures, callbacks, templates, ... devra être documenté un jour ou l'autre (que tu en arrives là ou pas, ce n'est pas le problème, mais si tu ne comptes pas en arriver là, c'est que tu es probablement sur le mauvais chemin). Imagine toi documenter un aspect: y en a t-il pour des pages ou est-ce torché en 10 lignes? C'est facile d'estimer ça lorsqu'on implémente une fonctionnalité en bout de chaine, voir ça au plus haut niveau de l'API, ça demande une réflexion assez poussée. Ça veut dire pas mal de tentatives dont certaines finiront nécessairement à la poubelle.

Reply

Marsh Posté le 14-09-2013 à 23:34:01    

Très intéressante cette discussion.
Concernant l'histoire d'ajouter de la type safety à l'API, je suis pas certains que ca oblige à avoir une API verbeuse et/ou complexe. (bon en C++ ca va etre delicat de faire un truc clean, quoique possible mais le code de la librairie deviendra extreme en terme de metaprog .. apres y'a toujours la possiblité de faire un pseudo-DSL comme ton SIT_MenuStruct mais typé)

Reply

Marsh Posté le 18-09-2013 à 22:50:21    

un DSL en C++ qui ferait que la GUI deviennent declarative et non aps un amoncelement d'appel de methode serait pas mal. J'avais tenter un truc dans le genre y a qqs temps.

Reply

Marsh Posté le 19-09-2013 à 13:15:43    

Ces discussions m'ont bien fait avancé, même si j'ai gardé l'héritage et le polymorphisme. (sans en abuser)

 

Les 1ers paramètres des fonctions de création seront typés:

Code :
  1. (string widget_name, PXContainerWidget parent_widget,


tous les autres paramètres seront passés dans une string.

Code :
  1. ,PXAttributesStr attributs);


Je programme intensément actuellement, j'espère vous montrer quelque-chose dans 2 à 3 mois.


Message édité par Caffrey's le 19-09-2013 à 13:39:30
Reply

Sujets relatifs:

Leave a Replay

Make sure you enter the(*)required information where indicate.HTML code is not allowed