You can not select more than 25 topics Topics must start with a chinese character,a letter or number, can include dashes ('-') and can be up to 35 characters long.

main.cpp 13 kB

11 months ago
123456789101112131415161718192021222324252627282930313233343536373839404142434445464748495051525354555657585960616263646566676869707172737475767778798081828384858687888990919293949596979899100101102103104105106107108109110111112113114115116117118119120121122123124125126127128129130131132133134135136137138139140141142143144145146147148149150151152153154155156157158159160161162163164165166167168169170171172173174175176177178179180181182183184185186187188189190191192193194195196197198199200201202203204205206207208209210211212213214215216217218219220221222223224225226227228229230231232233234235236237238239240241242243244245246247248249250251252253254255256257258259260261262263264265266267268269270271272273274275276277278279280281282283284285286287288289290291292293294295296297298299300301302303304305306307308309310311312313314315316317318319320321322323324325326327328329330331332333334335336337338339340341342343344345346347348349350351352353354355
  1. #include <cstdint>
  2. #include <iostream>
  3. #include "FL/Fl.H"
  4. #include "FL/Fl_Double_Window.H"
  5. #include "FL/Fl_Tree.H"
  6. constexpr const int32_t kLeftMargin = 5;
  7. const char * const kTreeOpenXpm[] = {
  8. "11 11 3 1",
  9. ". c #fefefe",
  10. "# c #444444",
  11. "@ c #000000",
  12. "###########",
  13. "#.........#",
  14. "#.........#",
  15. "#....@....#",
  16. "#....@....#",
  17. "#..@@@@@..#",
  18. "#....@....#",
  19. "#....@....#",
  20. "#.........#",
  21. "#.........#",
  22. "###########"
  23. };
  24. const char * const kTreeCloseXpm[] = {
  25. "11 11 3 1",
  26. ". c #fefefe",
  27. "# c #444444",
  28. "@ c #000000",
  29. "###########",
  30. "#.........#",
  31. "#.........#",
  32. "#.........#",
  33. "#.........#",
  34. "#..@@@@@..#",
  35. "#.........#",
  36. "#.........#",
  37. "#.........#",
  38. "#.........#",
  39. "###########"
  40. };
  41. // DERIVE CUSTOM CLASS FROM Fl_Tree_Item TO SHOW DATA IN COLUMNS
  42. class TreeRowItem : public Fl_Tree_Item {
  43. public:
  44. TreeRowItem(Fl_Tree *tree, const char *text) : Fl_Tree_Item(tree) {
  45. this->label(text);
  46. }
  47. int draw_item_content(int render);
  48. };
  49. // Small convenience class to handle adding columns.
  50. // TreeRowItem does most of the work.
  51. //
  52. class TreeWithColumns : public Fl_Tree {
  53. bool colseps_flag; // enable/disable column separator lines
  54. bool resizing_flag; // enable/disable interactive resizing
  55. char col_char; // column delimiter character
  56. int *col_widths; // array of column widths (supplied by application)
  57. int first_col_minw; // minimum width of first column
  58. int drag_col; // internal: column being FL_DRAG'ed
  59. Fl_Cursor last_cursor; // internal: last mouse cursor value
  60. protected:
  61. int column_near_mouse() {
  62. // Event not inside browser area? (eg. scrollbar) Early exit
  63. if ( !Fl::event_inside(_tix,_tiy,_tiw,_tih) ) return(-1);
  64. int mousex = Fl::event_x() + hposition();
  65. int colx = x() + first_col_minw;
  66. for ( int t=0; col_widths[t]; t++ ) {
  67. colx += col_widths[t];
  68. int diff = mousex - colx;
  69. // Mouse near column? Return column #
  70. if ( diff >= -4 && diff <= 4 ) return(t);
  71. }
  72. return(-1);
  73. }
  74. // Change the mouse cursor
  75. // Does nothing if cursor already set to same value.
  76. //
  77. void change_cursor(Fl_Cursor newcursor) {
  78. if ( newcursor == last_cursor ) return;
  79. window()->cursor(newcursor);
  80. last_cursor = newcursor;
  81. }
  82. public:
  83. TreeWithColumns(int X,int Y,int W,int H,const char *L=0) : Fl_Tree(X,Y,W,H,L) {
  84. colseps_flag = true;
  85. resizing_flag = true;
  86. col_char = '\t';
  87. col_widths = 0;
  88. first_col_minw = 80;
  89. drag_col = -1;
  90. last_cursor = FL_CURSOR_DEFAULT;
  91. // We need the default tree icons on all platforms.
  92. // For some reason someone made Fl_Tree have different icons on the Mac,
  93. // which doesn't look good for this application, so we force the icons
  94. // to be consistent with the '+' and '-' icons and dotted connection lines.
  95. //
  96. connectorstyle(FL_TREE_CONNECTOR_DOTTED);
  97. openicon(new Fl_Pixmap(tree_open_xpm));
  98. closeicon(new Fl_Pixmap(tree_close_xpm));
  99. }
  100. // The minimum width of column #1 in pixels.
  101. // During interactive resizing, don't allow first column to be smaller than this.
  102. //
  103. int first_column_minw() { return first_col_minw; }
  104. void first_column_minw(int val) { first_col_minw = val; }
  105. // Enable/disable the vertical column lines
  106. void column_separators(bool val) { colseps_flag = val; }
  107. bool column_separators() const { return colseps_flag; }
  108. // Enable/disable the vertical column lines
  109. void resizing(bool val) { resizing_flag = val; }
  110. bool resizing() const { return resizing_flag; }
  111. // Change the column delimiter character
  112. void column_char(char val) { this->col_char = val; }
  113. char column_char() const { return col_char; }
  114. // Set the column array.
  115. // Make sure the last entry is zero.
  116. // User allocated array must remain allocated for lifetime of class instance.
  117. // Must be large enough for all columns in data!
  118. //
  119. void column_widths(int *val) { this->col_widths = val; }
  120. int *column_widths() const { return col_widths; }
  121. TreeRowItem *AddRow(const char *s, TreeRowItem *parent_item=0) {
  122. TreeRowItem *item = new TreeRowItem(this, s); // create new item
  123. if ( parent_item == 0 ) { // wants root item as parent?
  124. if ( strcmp(root()->label(), "ROOT")==0 ) { // default root item?
  125. this->root(item); // make this item the new root
  126. // Special colors for root item -- this is the "header"
  127. item->labelfgcolor(0xffffff00);
  128. item->labelbgcolor(0x8888ff00);
  129. return item;
  130. } else {
  131. parent_item = (TreeRowItem*)root(); // use root as parent
  132. }
  133. }
  134. parent_item->add(prefs(), "", item); // add item to hierarchy
  135. return item; // return the new item
  136. }
  137. // Manage column resizing
  138. int handle(int e) {
  139. if ( !resizing_flag ) return Fl_Tree::handle(e); // resizing off? early exit
  140. // Handle column resizing
  141. int ret = 0;
  142. switch ( e ) {
  143. case FL_ENTER:
  144. ret = 1;
  145. break;
  146. case FL_MOVE:
  147. change_cursor( (column_near_mouse() >= 0) ? FL_CURSOR_WE : FL_CURSOR_DEFAULT);
  148. ret = 1;
  149. break;
  150. case FL_PUSH: {
  151. int whichcol = column_near_mouse();
  152. if ( whichcol >= 0 ) {
  153. // Clicked on resizer? Start dragging that column
  154. drag_col = whichcol;
  155. change_cursor(FL_CURSOR_DEFAULT);
  156. return 1; // eclipse event from Fl_Tree's handle()
  157. }
  158. break;
  159. }
  160. case FL_DRAG:
  161. if ( drag_col != -1 ) {
  162. // Sum up column widths to determine position
  163. int mousex = Fl::event_x() + hposition();
  164. int newwidth = mousex - (x() + first_column_minw());
  165. for ( int t=0; col_widths[t] && t<drag_col; t++ )
  166. { newwidth -= col_widths[t]; }
  167. // Apply new width, redraw interface
  168. col_widths[drag_col] = newwidth;
  169. if ( col_widths[drag_col] < 2 ) col_widths[drag_col] = 2; // XXX: 2 should be a class member
  170. recalc_tree();
  171. redraw();
  172. return 1; // eclipse event from Fl_Tree's handle()
  173. }
  174. break;
  175. case FL_LEAVE:
  176. case FL_RELEASE:
  177. change_cursor(FL_CURSOR_DEFAULT); // ensure normal cursor
  178. if ( drag_col != -1 && e == FL_RELEASE ) { // release during drag mode?
  179. drag_col = -1; // disable drag mode
  180. return 1; // eclipse event from base class; we handled it
  181. }
  182. drag_col = -1;
  183. ret = 1;
  184. break;
  185. }
  186. return(Fl_Tree::handle(e) ? 1 : ret);
  187. }
  188. // Hide these base class methods from the API; we don't want app using them,
  189. // as we expect all items in the tree to be TreeRowItems, not Fl_Tree_Items.
  190. private:
  191. using Fl_Tree::add;
  192. };
  193. // Handle custom drawing of the item
  194. //
  195. // All we're responsible for is drawing the 'label' area of the item
  196. // and it's background. Fl_Tree gives us a hint as to what the
  197. // foreground and background colors should be via the fg/bg parameters,
  198. // and whether we're supposed to render anything or not.
  199. //
  200. // The only other thing we must do is return the maximum X position
  201. // of scrollable content, i.e. the right most X position of content
  202. // that we want the user to be able to use the horizontal scrollbar
  203. // to reach.
  204. //
  205. int TreeRowItem::draw_item_content(int render) {
  206. TreeWithColumns *treewc = (TreeWithColumns*)tree();
  207. Fl_Color fg = drawfgcolor();
  208. Fl_Color bg = drawbgcolor();
  209. const int *col_widths = treewc->column_widths();
  210. // Show the date and time as two small strings
  211. // one on top of the other in a single item.
  212. //
  213. // Our item's label dimensions
  214. int X = label_x(), Y = label_y(),
  215. W = label_w(), H = label_h(),
  216. RX = treewc->x() - treewc->hposition() + treewc->first_column_minw(), // start first column at a fixed position
  217. RY = Y+H-fl_descent(); // text draws here
  218. // Render background
  219. if ( render ) {
  220. if ( is_selected() ) { fl_draw_box(prefs().selectbox(),X,Y,W,H,bg); }
  221. else { fl_color(bg); fl_rectf(X,Y,W,H); }
  222. fl_font(labelfont(), labelsize());
  223. }
  224. if ( render ) fl_push_clip(X,Y,W,H);
  225. // Render columns
  226. // ⚠️ Be sure to conditionally call fl_pop_clip() before return() from this section
  227. //
  228. {
  229. // Draw each column
  230. // ..or if not rendering, at least calculate width of row so we can return it.
  231. //
  232. int t=0;
  233. const char *s = label();
  234. char delim_str[2] = { treewc->column_char(), 0 }; // strcspn() wants a char[]
  235. while ( *s ) {
  236. int n = strcspn(s, delim_str); // find index to next delimiter char in 's' (or eos if none)
  237. if ( n>0 && render ) { // renderable string with at least 1 or more chars?
  238. int XX = ( t==0 ) ? X : RX; // TBD: Rename XX to something more meaningful
  239. // Don't clip last column.
  240. // See if there's more columns after this one; if so, clip the column.
  241. // If not, let column run to edge of widget
  242. //
  243. int CW = col_widths[t]; // clip width based on column width
  244. // If first column, clip to 2nd column's left edge
  245. if ( t==0 ) { CW = (RX+col_widths[0])-XX; }
  246. // If last column, clip to right edge of widget
  247. if ( *(s+n) == 0 ) { CW = (x()+w()-XX); }
  248. // Draw the text
  249. // We want first field (PID) indented, rest of fields fixed column.
  250. //
  251. fl_color(fg);
  252. fl_push_clip(XX, Y, CW, H); // prevent text from running into next column
  253. fl_draw(s, n, XX+LEFT_MARGIN, RY);
  254. fl_pop_clip(); // clip off
  255. // Draw vertical lines for all columns except first
  256. if ( t>0 && treewc->column_separators() ) {
  257. fl_color(FL_BLACK);
  258. fl_line(RX,Y,RX,Y+H);
  259. }
  260. }
  261. if ( *(s+n) == treewc->column_char() ) {
  262. s += n+1; // skip field + delim
  263. RX += col_widths[t++]; // keep track of fixed column widths for all except right column
  264. continue;
  265. } else {
  266. // Last field? Return entire length of unclipped field
  267. RX += fl_width(s) + LEFT_MARGIN;
  268. s += n;
  269. }
  270. }
  271. }
  272. if ( render ) fl_pop_clip();
  273. return RX; // return right most edge of what we've rendered
  274. }
  275. int main(int argc, char *argv[]) {
  276. Fl::scheme("gtk+");
  277. Fl_Double_Window *win = new Fl_Double_Window(550, 400, "Tree With Columns");
  278. win->begin();
  279. {
  280. static int col_widths[] = { 80, 50, 50, 50, 0 };
  281. // Create the tree
  282. TreeWithColumns *tree = new TreeWithColumns(0, 0, win->w(), win->h());
  283. tree->selectmode(FL_TREE_SELECT_MULTI); // multiselect
  284. tree->column_widths(col_widths); // set column widths array
  285. tree->resizing(true); // enable interactive resizing
  286. tree->column_char('\t'); // use tab char as column delimiter
  287. tree->first_column_minw(100); // minimum width of first column
  288. // Add some items in a hierarchy:
  289. //
  290. // 5197 ? Ss 0:00 /usr/sbin/sshd -D
  291. // 12807 ? Ss 0:00 \_ sshd: root@pts/6
  292. // 12811 pts/6 Ss+ 0:00 | \_ -tcsh
  293. // 3993 ? Ss 0:00 \_ sshd: erco [priv]
  294. // 3997 ? S 0:00 \_ sshd: erco@pts/14
  295. // 3998 pts/14 Ss+ 0:00 \_ -tcsh
  296. // 5199 ? Ssl 8:02 /usr/sbin/rsyslogd -n
  297. // 5375 ? Ss 0:00 /usr/sbin/atd -f
  298. // 13778 ? Ss 0:00 \_ /usr/sbin/atd -f
  299. // 13781 ? SN 0:00 | \_ sh
  300. // 13785 ? SN 684:59 | \_ xterm -title Reminder: now -geometry 40x6-1+1 -fg white -bg #cc0000 -hold -font
  301. //
  302. TreeRowItem *top; // top of hierarchy
  303. // First row is header
  304. top = tree->AddRow("PID\tTTY\tSTAT\tTIME\tCOMMAND"); // first row is always the "header"
  305. // Add the hierarchical rows of data..
  306. top = tree->AddRow("5197\t?\tSs\t0:00\t/usr/sbin/sshd -D"); // top level
  307. // Add 200 copies of data to tree to test performance
  308. for ( int t=0; t<200; t++ ) {
  309. {
  310. TreeRowItem *child1 = tree->AddRow("12807\t?\tSs\t0:00\tsshd: root@pts/6", top);
  311. {
  312. tree->AddRow("12811\tpts/6\tSs+\t0:00\t-tcsh", child1);
  313. }
  314. child1 = tree->AddRow("3993\t?\tSs\t0:00\tsshd: erco [priv]", top);
  315. {
  316. TreeRowItem *child2 = tree->AddRow("3997\t?\tS\t0:00\tsshd: erco@pts/14", child1);
  317. {
  318. tree->AddRow("3998\tpts/14\tSs+\t0:00\t-tcsh", child2);
  319. }
  320. }
  321. }
  322. top = tree->AddRow("5199\t?\tSsl\t8:02\t/usr/sbin/rsyslogd -n");
  323. top = tree->AddRow("5375\t?\tSs\t0:00\t/usr/sbin/atd -f");
  324. {
  325. TreeRowItem *child1 = tree->AddRow("13778\t?\tSs\t0:00\t/usr/sbin/atd -f", top);
  326. {
  327. TreeRowItem *child2 = tree->AddRow("13781\t?\tSN\t0:00\tsh", child1);
  328. {
  329. tree->AddRow("13785\t?\tSN\t684:59\txterm -title Reminder: now -geometry 40x6-1+1 -fg white -bg #cc0000 -hold -font", child2);
  330. }
  331. }
  332. }
  333. }
  334. // tree->show_self();
  335. }
  336. win->end();
  337. win->resizable(win);
  338. win->show(argc, argv);
  339. return(Fl::run());
  340. }