pax_global_header00006660000000000000000000000064146317041350014516gustar00rootroot0000000000000052 comment=ec6456e8e3da67cf8e7d2877541dbe265eda9eba robtk-0.8.5/000077500000000000000000000000001463170413500126515ustar00rootroot00000000000000robtk-0.8.5/AUTHORS000066400000000000000000000000401463170413500137130ustar00rootroot00000000000000Robin Gareus robtk-0.8.5/COPYING000066400000000000000000000355641463170413500137210ustar00rootroot00000000000000 GNU GENERAL PUBLIC LICENSE Version 2, June 1991 Copyright (C) 1989, 1991 Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301 USA Everyone is permitted to copy and distribute verbatim copies of this license document, but changing it is not allowed. Preamble The licenses for most software are designed to take away your freedom to share and change it. By contrast, the GNU General Public License is intended to guarantee your freedom to share and change free software--to make sure the software is free for all its users. This General Public License applies to most of the Free Software Foundation's software and to any other program whose authors commit to using it. (Some other Free Software Foundation software is covered by the GNU Lesser General Public License instead.) You can apply it to your programs, too. When we speak of free software, we are referring to freedom, not price. Our General Public Licenses are designed to make sure that you have the freedom to distribute copies of free software (and charge for this service if you wish), that you receive source code or can get it if you want it, that you can change the software or use pieces of it in new free programs; and that you know you can do these things. To protect your rights, we need to make restrictions that forbid anyone to deny you these rights or to ask you to surrender the rights. These restrictions translate to certain responsibilities for you if you distribute copies of the software, or if you modify it. For example, if you distribute copies of such a program, whether gratis or for a fee, you must give the recipients all the rights that you have. You must make sure that they, too, receive or can get the source code. And you must show them these terms so they know their rights. We protect your rights with two steps: (1) copyright the software, and (2) offer you this license which gives you legal permission to copy, distribute and/or modify the software. Also, for each author's protection and ours, we want to make certain that everyone understands that there is no warranty for this free software. If the software is modified by someone else and passed on, we want its recipients to know that what they have is not the original, so that any problems introduced by others will not reflect on the original authors' reputations. Finally, any free program is threatened constantly by software patents. We wish to avoid the danger that redistributors of a free program will individually obtain patent licenses, in effect making the program proprietary. To prevent this, we have made it clear that any patent must be licensed for everyone's free use or not licensed at all. The precise terms and conditions for copying, distribution and modification follow. GNU GENERAL PUBLIC LICENSE TERMS AND CONDITIONS FOR COPYING, DISTRIBUTION AND MODIFICATION 0. This License applies to any program or other work which contains a notice placed by the copyright holder saying it may be distributed under the terms of this General Public License. The "Program", below, refers to any such program or work, and a "work based on the Program" means either the Program or any derivative work under copyright law: that is to say, a work containing the Program or a portion of it, either verbatim or with modifications and/or translated into another language. (Hereinafter, translation is included without limitation in the term "modification".) Each licensee is addressed as "you". Activities other than copying, distribution and modification are not covered by this License; they are outside its scope. The act of running the Program is not restricted, and the output from the Program is covered only if its contents constitute a work based on the Program (independent of having been made by running the Program). Whether that is true depends on what the Program does. 1. You may copy and distribute verbatim copies of the Program's source code as you receive it, in any medium, provided that you conspicuously and appropriately publish on each copy an appropriate copyright notice and disclaimer of warranty; keep intact all the notices that refer to this License and to the absence of any warranty; and give any other recipients of the Program a copy of this License along with the Program. You may charge a fee for the physical act of transferring a copy, and you may at your option offer warranty protection in exchange for a fee. 2. You may modify your copy or copies of the Program or any portion of it, thus forming a work based on the Program, and copy and distribute such modifications or work under the terms of Section 1 above, provided that you also meet all of these conditions: a) You must cause the modified files to carry prominent notices stating that you changed the files and the date of any change. b) You must cause any work that you distribute or publish, that in whole or in part contains or is derived from the Program or any part thereof, to be licensed as a whole at no charge to all third parties under the terms of this License. c) If the modified program normally reads commands interactively when run, you must cause it, when started running for such interactive use in the most ordinary way, to print or display an announcement including an appropriate copyright notice and a notice that there is no warranty (or else, saying that you provide a warranty) and that users may redistribute the program under these conditions, and telling the user how to view a copy of this License. (Exception: if the Program itself is interactive but does not normally print such an announcement, your work based on the Program is not required to print an announcement.) These requirements apply to the modified work as a whole. If identifiable sections of that work are not derived from the Program, and can be reasonably considered independent and separate works in themselves, then this License, and its terms, do not apply to those sections when you distribute them as separate works. But when you distribute the same sections as part of a whole which is a work based on the Program, the distribution of the whole must be on the terms of this License, whose permissions for other licensees extend to the entire whole, and thus to each and every part regardless of who wrote it. Thus, it is not the intent of this section to claim rights or contest your rights to work written entirely by you; rather, the intent is to exercise the right to control the distribution of derivative or collective works based on the Program. In addition, mere aggregation of another work not based on the Program with the Program (or with a work based on the Program) on a volume of a storage or distribution medium does not bring the other work under the scope of this License. 3. You may copy and distribute the Program (or a work based on it, under Section 2) in object code or executable form under the terms of Sections 1 and 2 above provided that you also do one of the following: a) Accompany it with the complete corresponding machine-readable source code, which must be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, b) Accompany it with a written offer, valid for at least three years, to give any third party, for a charge no more than your cost of physically performing source distribution, a complete machine-readable copy of the corresponding source code, to be distributed under the terms of Sections 1 and 2 above on a medium customarily used for software interchange; or, c) Accompany it with the information you received as to the offer to distribute corresponding source code. (This alternative is allowed only for noncommercial distribution and only if you received the program in object code or executable form with such an offer, in accord with Subsection b above.) The source code for a work means the preferred form of the work for making modifications to it. For an executable work, complete source code means all the source code for all modules it contains, plus any associated interface definition files, plus the scripts used to control compilation and installation of the executable. However, as a special exception, the source code distributed need not include anything that is normally distributed (in either source or binary form) with the major components (compiler, kernel, and so on) of the operating system on which the executable runs, unless that component itself accompanies the executable. If distribution of executable or object code is made by offering access to copy from a designated place, then offering equivalent access to copy the source code from the same place counts as distribution of the source code, even though third parties are not compelled to copy the source along with the object code. 4. You may not copy, modify, sublicense, or distribute the Program except as expressly provided under this License. Any attempt otherwise to copy, modify, sublicense or distribute the Program is void, and will automatically terminate your rights under this License. However, parties who have received copies, or rights, from you under this License will not have their licenses terminated so long as such parties remain in full compliance. 5. You are not required to accept this License, since you have not signed it. However, nothing else grants you permission to modify or distribute the Program or its derivative works. These actions are prohibited by law if you do not accept this License. Therefore, by modifying or distributing the Program (or any work based on the Program), you indicate your acceptance of this License to do so, and all its terms and conditions for copying, distributing or modifying the Program or works based on it. 6. Each time you redistribute the Program (or any work based on the Program), the recipient automatically receives a license from the original licensor to copy, distribute or modify the Program subject to these terms and conditions. You may not impose any further restrictions on the recipients' exercise of the rights granted herein. You are not responsible for enforcing compliance by third parties to this License. 7. If, as a consequence of a court judgment or allegation of patent infringement or for any other reason (not limited to patent issues), conditions are imposed on you (whether by court order, agreement or otherwise) that contradict the conditions of this License, they do not excuse you from the conditions of this License. If you cannot distribute so as to satisfy simultaneously your obligations under this License and any other pertinent obligations, then as a consequence you may not distribute the Program at all. For example, if a patent license would not permit royalty-free redistribution of the Program by all those who receive copies directly or indirectly through you, then the only way you could satisfy both it and this License would be to refrain entirely from distribution of the Program. If any portion of this section is held invalid or unenforceable under any particular circumstance, the balance of the section is intended to apply and the section as a whole is intended to apply in other circumstances. It is not the purpose of this section to induce you to infringe any patents or other property right claims or to contest validity of any such claims; this section has the sole purpose of protecting the integrity of the free software distribution system, which is implemented by public license practices. Many people have made generous contributions to the wide range of software distributed through that system in reliance on consistent application of that system; it is up to the author/donor to decide if he or she is willing to distribute software through any other system and a licensee cannot impose that choice. This section is intended to make thoroughly clear what is believed to be a consequence of the rest of this License. 8. If the distribution and/or use of the Program is restricted in certain countries either by patents or by copyrighted interfaces, the original copyright holder who places the Program under this License may add an explicit geographical distribution limitation excluding those countries, so that distribution is permitted only in or among countries not thus excluded. In such case, this License incorporates the limitation as if written in the body of this License. 9. The Free Software Foundation may publish revised and/or new versions of the General Public License from time to time. Such new versions will be similar in spirit to the present version, but may differ in detail to address new problems or concerns. Each version is given a distinguishing version number. If the Program specifies a version number of this License which applies to it and "any later version", you have the option of following the terms and conditions either of that version or of any later version published by the Free Software Foundation. If the Program does not specify a version number of this License, you may choose any version ever published by the Free Software Foundation. 10. If you wish to incorporate parts of the Program into other free programs whose distribution conditions are different, write to the author to ask for permission. For software which is copyrighted by the Free Software Foundation, write to the Free Software Foundation; we sometimes make exceptions for this. Our decision will be guided by the two goals of preserving the free status of all derivatives of our free software and of promoting the sharing and reuse of software generally. NO WARRANTY 11. BECAUSE THE PROGRAM IS LICENSED FREE OF CHARGE, THERE IS NO WARRANTY FOR THE PROGRAM, TO THE EXTENT PERMITTED BY APPLICABLE LAW. EXCEPT WHEN OTHERWISE STATED IN WRITING THE COPYRIGHT HOLDERS AND/OR OTHER PARTIES PROVIDE THE PROGRAM "AS IS" WITHOUT WARRANTY OF ANY KIND, EITHER EXPRESSED OR IMPLIED, INCLUDING, BUT NOT LIMITED TO, THE IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS FOR A PARTICULAR PURPOSE. THE ENTIRE RISK AS TO THE QUALITY AND PERFORMANCE OF THE PROGRAM IS WITH YOU. SHOULD THE PROGRAM PROVE DEFECTIVE, YOU ASSUME THE COST OF ALL NECESSARY SERVICING, REPAIR OR CORRECTION. 12. IN NO EVENT UNLESS REQUIRED BY APPLICABLE LAW OR AGREED TO IN WRITING WILL ANY COPYRIGHT HOLDER, OR ANY OTHER PARTY WHO MAY MODIFY AND/OR REDISTRIBUTE THE PROGRAM AS PERMITTED ABOVE, BE LIABLE TO YOU FOR DAMAGES, INCLUDING ANY GENERAL, SPECIAL, INCIDENTAL OR CONSEQUENTIAL DAMAGES ARISING OUT OF THE USE OR INABILITY TO USE THE PROGRAM (INCLUDING BUT NOT LIMITED TO LOSS OF DATA OR DATA BEING RENDERED INACCURATE OR LOSSES SUSTAINED BY YOU OR THIRD PARTIES OR A FAILURE OF THE PROGRAM TO OPERATE WITH ANY OTHER PROGRAMS), EVEN IF SUCH HOLDER OR OTHER PARTY HAS BEEN ADVISED OF THE POSSIBILITY OF SUCH DAMAGES. END OF TERMS AND CONDITIONS robtk-0.8.5/README.md000066400000000000000000000026661463170413500141420ustar00rootroot00000000000000robtk -- robin's LV2 UI ToolKit =============================== robtk facilitates creating LV2 plugins UIs with emphasis to allow porting existing gtk+ plugin UIs. robtk provides implementations for these existing gtk+ widgets: * label * separator * push button * toggle button * radio button * spin box * text combo-box * drawing-area as well as gtk+ container and layout objects: * horizontal box * vertical box * table layout and additional widgets * x/y plot area * rgb/rgba image * (volume, gain) slider * multi-state button A subset of gtk's functionality and widgets were re-implemented in cairo. On compile-time GTK+ as well as openGL variants of the UI can be produced. The complete toolkit consists of header files to be included with the UI source-code and maps functions to the underlying implementation e.g. `robtk_lbl_new()` to `gtk_label_new()`. no additional libraries or dependencies are required. Similar to widgets and layout, the event-structure and callbacks of robtk lean onto the GTK API providing * mouse-events: move, click up/down, scoll * widget-events: enter, leave * widget-allocation: size-request, allocate, position * window-events: resize, limit-size * widget-exposure: complete and partial redraw robtk includes LV2-GUI wrappers and gnu-make definitions for easy use in a LV2 project. Currently it is used by meters.lv2, sisco.lv2, tuna.lv2, mixtri.lv2, fil4.lv2 and setBfree robtk-0.8.5/gl/000077500000000000000000000000001463170413500132535ustar00rootroot00000000000000robtk-0.8.5/gl/common_cgl.h000066400000000000000000000041771463170413500155520ustar00rootroot00000000000000/* robwidget - gtk2 & GL wrapper * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef COMMON_CAIRO_H #define COMMON_CAIRO_H #include #include #include static PangoFontDescription * get_font_from_theme () { PangoFontDescription * rv; rv = pango_font_description_from_string("Sans 11px"); assert(rv); return rv; } static float host_fg_color[4] = { .9, .9, .9, 1.0 }; static float host_bg_color[4] = { .24, .24, .24, 1.0 }; static bool rtk_light_theme = false; static void set_host_color (int which, uint32_t color) { switch(which) { case 0: host_fg_color[0] = ((color >> 24) & 0xff) / 255.0; host_fg_color[1] = ((color >> 16) & 0xff) / 255.0; host_fg_color[2] = ((color >> 8) & 0xff) / 255.0; host_fg_color[3] = ((color >> 0) & 0xff) / 255.0; rtk_light_theme = luminance_rgb (host_fg_color) < 0.5; break; case 1: host_bg_color[0] = ((color >> 24) & 0xff) / 255.0; host_bg_color[1] = ((color >> 16) & 0xff) / 255.0; host_bg_color[2] = ((color >> 8) & 0xff) / 255.0; host_bg_color[3] = ((color >> 0) & 0xff) / 255.0; break; default: break; } } static void get_color_from_theme (int which, float *col) { switch(which) { default: // fg memcpy (col, host_fg_color, 4 * sizeof (float)); break; case 1: // bg memcpy (col, host_bg_color, 4 * sizeof (float)); break; } } static bool is_light_theme () { return rtk_light_theme; } #endif robtk-0.8.5/gl/layout.h000066400000000000000000000754741463170413500147620ustar00rootroot00000000000000/* robwidget - widget layout packing * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ /*****************************************************************************/ /* common container functions */ //#define DEBUG_HBOX //#define DEBUG_VBOX //#define DEBUG_TABLE #define RTK_SHRINK 0 #define RTK_EXPAND 1 #define RTK_FILL 2 #define RTK_EXANDF 3 struct rob_container { bool homogeneous; bool expand; int padding; }; struct rob_table_child { RobWidget *rw; int left; int right; int top; int bottom; int xpadding; int ypadding; int expand_x; int expand_y; }; struct rob_table_field { int req_w; int req_h; bool is_expandable_x; bool is_expandable_y; int acq_w; int acq_h; int expand; }; struct rob_table { bool homogeneous; bool expand; unsigned int nrows; unsigned int ncols; unsigned int nchilds; struct rob_table_child *chld; struct rob_table_field *rows; struct rob_table_field *cols; }; static void rob_table_resize(struct rob_table *rt, unsigned int nrows, unsigned int ncols) { if (rt->ncols >= ncols && rt->nrows >= nrows) return; #ifdef DEBUG_TABLE printf("rob_table_resize %d %d\n", nrows, ncols); #endif if (rt->nrows != nrows) { rt->rows = (struct rob_table_field*) realloc(rt->rows, sizeof(struct rob_table_field) * nrows); rt->nrows = nrows; } if (rt->ncols != ncols) { rt->cols = (struct rob_table_field*) realloc(rt->cols, sizeof(struct rob_table_field) * ncols); rt->ncols = ncols; } } static void robwidget_position_cache(RobWidget *rw) { RobTkBtnEvent e; e.x = 0; e.y = 0; offset_traverse_from_child(rw, &e); rw->trel.x = e.x; rw->trel.y = e.y; rw->trel.width = rw->area.width; rw->trel.height = rw->area.height; rw->resized = TRUE; } static void robwidget_position_set(RobWidget *rw, const int pw, const int ph) { assert (pw >= rw->area.width && ph >= rw->area.height); rw->area.x = rint((pw - rw->area.width) * rw->xalign); rw->area.y = rint((ph - rw->area.height) * rw->yalign); } static void rtable_size_allocate(RobWidget* rw, int w, int h); static void rhbox_size_allocate(RobWidget* rw, int w, int h); static void rvbox_size_allocate(RobWidget* rw, int w, int h); static bool roblayout_can_expand(RobWidget *rw) { bool can_expand = FALSE; if ( rw->size_allocate == rhbox_size_allocate || rw->size_allocate == rvbox_size_allocate) { can_expand = ((struct rob_container*)rw->self)->expand; } else if (rw->size_allocate == rtable_size_allocate) { can_expand = ((struct rob_table*)rw->self)->expand; } else if (rw->size_allocate) { can_expand = (rw->packing_opts & 1) ? TRUE : FALSE; } return can_expand; } static bool roblayout_can_fill(RobWidget *rw) { return (rw->packing_opts & 2); } static void rcontainer_child_pack(RobWidget *rw, RobWidget *chld, bool expand, bool fill) { #ifndef NDEBUG if (chld->parent) { fprintf(stderr, "re-parent child\n"); } #endif if ( chld->size_allocate == rhbox_size_allocate || chld->size_allocate == rvbox_size_allocate ) { ((struct rob_container*)chld->self)->expand = expand; } if (chld->size_allocate == rtable_size_allocate) { ((struct rob_table*)chld->self)->expand = expand; } chld->packing_opts = (expand ? 1 : 0) | (fill ? 2 : 0); rw->children = (RobWidget**) realloc(rw->children, (rw->childcount + 1) * sizeof(RobWidget *)); rw->children[rw->childcount] = chld; rw->childcount++; chld->parent = rw; } static void rcontainer_clear_bg(RobWidget* rw, cairo_t* cr, cairo_rectangle_t *ev) { float c[4]; #ifdef VISIBLE_EXPOSE c[0] = rand() / (float)RAND_MAX; c[1] = rand() / (float)RAND_MAX; c[2] = rand() / (float)RAND_MAX; c[3] = 1.0; #else get_color_from_theme(1, c); #endif cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb (cr, c[0], c[1], c[2]); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); #if 0 cairo_fill_preserve(cr); cairo_set_source_rgb (cr, 1.0, 0.1, .1); cairo_stroke(cr); #else cairo_fill(cr); #endif } /*****************************************************************************/ /* common container functions, for event propagation */ #define MOUSEEVENT \ RobTkBtnEvent event; \ event.x = ev->x - c->area.x; \ event.y = ev->y - c->area.y; \ event.state = ev->state; \ event.direction = ev->direction; \ event.button = ev->button; static RobWidget* rcontainer_mousedown(RobWidget* handle, RobTkBtnEvent *ev) { RobWidget * rw = (RobWidget*)handle; if (rw->block_events ) return NULL; RobWidget * c = robwidget_child_at(rw, ev->x, ev->y); if (!c || !c->mousedown) return NULL; if (c->hidden) return NULL; MOUSEEVENT return c->mousedown(c, &event); } static RobWidget* rcontainer_mouseup(RobWidget* handle, RobTkBtnEvent *ev) { RobWidget * rw = (RobWidget*)handle; if (rw->block_events ) return NULL; RobWidget * c = robwidget_child_at(rw, ev->x, ev->y); if (!c || !c->mouseup) return NULL; if (c->hidden) return NULL; MOUSEEVENT return c->mouseup(c, &event); } static RobWidget* rcontainer_mousemove(RobWidget* handle, RobTkBtnEvent *ev) { RobWidget * rw = (RobWidget*)handle; if (rw->block_events ) return NULL; RobWidget * c = robwidget_child_at(rw, ev->x, ev->y); if (!c || !c->mousemove) return NULL; if (c->hidden) return NULL; MOUSEEVENT return c->mousemove(c, &event); } static RobWidget* rcontainer_mousescroll(RobWidget* handle, RobTkBtnEvent *ev) { RobWidget * rw = (RobWidget*)handle; if (rw->block_events ) return NULL; RobWidget * c = robwidget_child_at(rw, ev->x, ev->y); if (!c || !c->mousescroll) return NULL; if (c->hidden) return NULL; MOUSEEVENT return c->mousescroll(c, &event); } static bool rcontainer_expose_event_no_clear(RobWidget* rw, cairo_t* cr, cairo_rectangle_t *ev) { for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; cairo_rectangle_t event; if (c->hidden) continue; if (!rect_intersect(&c->area, ev)) continue; if (rw->resized) { memcpy(&event, ev, sizeof(cairo_rectangle_t)); } else { event.x = MAX(0, ev->x - c->area.x); event.y = MAX(0, ev->y - c->area.y); event.width = MIN(c->area.x + c->area.width, ev->x + ev->width) - MAX(ev->x, c->area.x); event.height = MIN(c->area.y + c->area.height, ev->y + ev->height) - MAX(ev->y, c->area.y); } #ifdef DEBUG_EXPOSURE_UI printf("rce %.1f+%.1f , %.1fx%.1f || cld %.1f+%.1f , %.1fx%.1f || ISC %.1f+%.1f , %.1fx%.1f\n", ev->x, ev->y, ev->width, ev->height, c->area.x, c->area.y, c->area.width, c->area.height, event.x, event.y, event.width, event.height); #endif cairo_save(cr); cairo_translate(cr, c->area.x, c->area.y); c->expose_event(c, cr, &event); #if 0 // VISUAL LAYOUT DEBUG -- NB. expose_event may or may not alter event cairo_rectangle(cr, event.x, event.y, event.width, event.height); cairo_set_source_rgba(cr, rand()/(float)RAND_MAX, rand()/(float)RAND_MAX, rand()/(float)RAND_MAX, 0.5); cairo_fill(cr); #endif cairo_restore(cr); } if (rw->resized) { rw->resized = FALSE; } return TRUE; } static bool rcontainer_expose_event(RobWidget* rw, cairo_t* cr, cairo_rectangle_t *ev) { if (rw->resized) { #if 0 cairo_rectangle_t event; event.x = MAX(0, ev->x - rw->area.x); event.y = MAX(0, ev->y - rw->area.y); event.width = MIN(rw->area.x + rw->area.width , ev->x + ev->width) - MAX(ev->x, rw->area.x); event.height = MIN(rw->area.y + rw->area.height, ev->y + ev->height) - MAX(ev->y, rw->area.y); cairo_save(cr); rcontainer_clear_bg(rw, cr, &event); cairo_restore(cr); #else cairo_save(cr); cairo_rectangle_t event; event.x = 0; event.y = 0; event.width = rw->area.width; event.height = rw->area.height; rcontainer_clear_bg(rw, cr, &event); cairo_restore(cr); #endif } return rcontainer_expose_event_no_clear(rw, cr, ev); } /*****************************************************************************/ /* specific containers */ /* horizontal box */ static void rhbox_size_request(RobWidget* rw, int *w, int *h) { assert(w && h); int ww = 0; int hh = 0; bool homogeneous = ((struct rob_container*)rw->self)->homogeneous; int padding = ((struct rob_container*)rw->self)->padding; int cnt = 0; for (unsigned int i=0; i < rw->childcount; ++i) { int cw, ch; RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; c->size_request(c, &cw, &ch); if (homogeneous) { ww = MAX(cw, ww); } else { ww += cw; } hh = MAX(ch, hh); c->area.width = cw; c->area.height = ch; cnt++; #ifdef DEBUG_HBOX printf("HBOXCHILD %d wants %dx%d (new total: %dx%d)\n", i, cw, ch, ww, hh); #endif } if (homogeneous) { for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; c->area.width = ww; } ww *= cnt; #ifdef DEBUG_HBOX printf("HBOXCHILD set homogenious x %d (total: %dx%d)\n", cnt, ww, hh); #endif } if (cnt > 0) { ww += (cnt-1) * padding; } ww = ceil(ww); hh = ceil(hh); *w = ww; *h = hh; robwidget_set_area(rw, 0, 0, ww, hh); } static void rhbox_size_allocate(RobWidget* rw, int w, int h) { #ifdef DEBUG_HBOX printf("Hbox size_allocate %d, %d\n", w, h); #endif int padding = ((struct rob_container*)rw->self)->padding; bool expand = ((struct rob_container*)rw->self)->expand; if (w < rw->area.width) { printf(" !!! hbox packing error alloc:%d, widget:%.1f\n", w, rw->area.width); w = rw->area.width; } float xtra_space = 0; bool grow = FALSE; if (w > rw->area.width) { // check what widgets can expand.. int cnt = 0; int exp = 0; for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; ++cnt; if (!roblayout_can_expand(c)) continue; if (c->size_allocate) { ++exp;} } if (exp > 0) { /* divide extra space.. */ xtra_space = (w - rw->area.width /* - (cnt-1) * padding */) / (float)exp; #ifdef DEBUG_HBOX printf("expand %d widgets by %.1f width\n", exp, xtra_space); #endif } else if (!rw->position_set) { xtra_space = (w - rw->area.width) / 2.0; grow = TRUE; #ifdef DEBUG_HBOX printf("grow self by %.1f width\n", xtra_space); #endif } else { #ifdef DEBUG_HBOX printf("don't grow\n"); #endif } } const int hh = rw->area.height; /* allocate kids */ for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; if (c->size_allocate) { c->size_allocate(c, c->area.width + ((grow || !roblayout_can_expand(c)) ? 0 : floorf(xtra_space)), roblayout_can_fill(c) ? h : hh); } #ifdef DEBUG_HBOX printf("HBOXCHILD %d use %.1fx%.1f\n", i, c->area.width, c->area.height); #endif } /* set position after allocation */ float ww = grow ? xtra_space : 0; int ccnt = 0; for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; if (++ccnt != 1) { ww += padding; } if (c->position_set) { c->position_set(c, c->area.width, h); } else { robwidget_position_set(c, c->area.width, h); } c->area.x += floorf(ww); c->area.y += 0; if (!roblayout_can_fill(c)) { c->area.y += roblayout_can_expand(c) ? 0 : floor((hh - h) / 2.0); } ww += c->area.width; #ifdef DEBUG_HBOX printf("HBOXCHILD %d '%s' packed to %.1f+%.1f %.1fx%.1f\n", i, ROBWIDGET_NAME(c), c->area.x, c->area.y, c->area.width, c->area.height); #endif if (c->redraw_pending) { queue_draw(c); } } #ifdef DEBUG_HBOX if (grow) ww += xtra_space; if (ww != w) { printf("MISSED STH :) width: %.1f <> parent-w: %d\n", ww, w); } #endif if (expand) { ww = w; } else { ww = rint(ww); } robwidget_set_area(rw, 0, 0, ww, h); } static void rob_hbox_child_pack(RobWidget *rw, RobWidget *chld, bool expand, bool fill) { rcontainer_child_pack(rw, chld, expand, fill); } static RobWidget * rob_hbox_new(bool homogeneous, int padding) { RobWidget * rw = robwidget_new(NULL); ROBWIDGET_SETNAME(rw, "hbox"); rw->self = (struct rob_container*) calloc(1, sizeof(struct rob_container)); ((struct rob_container*)rw->self)->homogeneous = homogeneous; ((struct rob_container*)rw->self)->padding = padding; ((struct rob_container*)rw->self)->expand = TRUE; rw->size_request = rhbox_size_request; rw->size_allocate = rhbox_size_allocate; rw->expose_event = rcontainer_expose_event; rw->mouseup = rcontainer_mouseup; rw->mousedown = rcontainer_mousedown; rw->mousemove = rcontainer_mousemove; rw->mousemove = rcontainer_mousemove; rw->mousescroll = rcontainer_mousescroll; rw->area.x=0; rw->area.y=0; rw->area.width = 0; rw->area.height = 0; return rw; } /* vertical box */ static void rvbox_size_request(RobWidget* rw, int *w, int *h) { assert(w && h); int ww = 0; int hh = 0; bool homogeneous = ((struct rob_container*)rw->self)->homogeneous; int padding = ((struct rob_container*)rw->self)->padding; int cnt = 0; for (unsigned int i=0; i < rw->childcount; ++i) { int cw, ch; RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; c->size_request(c, &cw, &ch); ww = MAX(cw, ww); if (homogeneous) { hh = MAX(ch, hh); } else { hh += ch; } c->area.width = cw; c->area.height = ch; cnt++; #ifdef DEBUG_VBOX printf("VBOXCHILD %d ('%s') wants %dx%d (new total: %dx%d)\n", i, ROBWIDGET_NAME(rw->children[i]), cw, ch, ww, hh); #endif } if (homogeneous) { for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; c->area.height = hh; } hh *= cnt; } if (cnt > 0) { hh += (cnt-1) * padding; } ww = ceil(ww); hh = ceil(hh); *w = ww; *h = hh; robwidget_set_area(rw, 0, 0, ww, hh); #ifdef DEBUG_VBOX printf("VBOX request %d, %d (%.1f, %.1f)\n", ww, hh, rw->area.width, rw->area.height); #endif } static void rvbox_size_allocate(RobWidget* rw, int w, int h) { #ifdef DEBUG_VBOX printf("rvbox_size_allocate %s: %d, %d (%.1f, %.1f)\n", ROBWIDGET_NAME(rw), w, h, rw->area.width, rw->area.height); #endif int padding = ((struct rob_container*)rw->self)->padding; bool expand = ((struct rob_container*)rw->self)->expand; if (h < rw->area.height) { printf(" !!! vbox packing error alloc:%d, widget:%.1f\n", h, rw->area.height); h = rw->area.height; } float xtra_space = 0; bool grow = FALSE; if (h > rw->area.height) { // check what widgets can expand.. int cnt = 0; int exp = 0; for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; ++cnt; if (!roblayout_can_expand(c)) continue; if (c->size_allocate) { ++exp;} } if (exp > 0) { /* divide extra space.. */ xtra_space = (h - rw->area.height /*- (cnt-1) * padding*/) / (float) exp; #ifdef DEBUG_VBOX printf("expand %d widgets by %.1f height (%d, %d)\n", exp, xtra_space, cnt, padding); #endif } else if (!rw->position_set) { xtra_space = (h - rw->area.height) / 2.0; grow = TRUE; #ifdef DEBUG_VBOX printf("grow self by %.1f height\n", xtra_space); #endif } else { #ifdef DEBUG_VBOX printf("don't grow\n"); #endif } } const int ww = rw->area.width; /* allocate kids */ for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; if (c->size_allocate) { c->size_allocate(c, roblayout_can_expand(c) ? w : ww, c->area.height + ((grow || !roblayout_can_expand(c)) ? 0 : floorf(xtra_space))); } #ifdef DEBUG_VBOX printf("VBOXCHILD %d ('%s') use %.1fx%.1f\n", i, ROBWIDGET_NAME(rw->children[i]), c->area.width, c->area.height); #endif } /* set position after allocation */ float hh = grow ? xtra_space : 0; int ccnt = 0; for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; if (++ccnt != 1) { hh += padding; } if (c->position_set) { c->position_set(c, w, c->area.height); } else { robwidget_position_set(c, w, c->area.height); } if (!roblayout_can_fill(c)) { c->area.x += roblayout_can_expand(c) ? 0 : floor((ww - w) / 2.0); } c->area.y += floorf(hh); hh += c->area.height; #ifdef DEBUG_VBOX printf("VBOXCHILD %d packed to %.1f+%.1f %.1fx%.1f\n", i, c->area.x, c->area.y, c->area.width, c->area.height); #endif if (c->redraw_pending) { queue_draw(c); } } #ifdef DEBUG_VBOX if (grow) hh += xtra_space; if (hh != h) { printf("MISSED STH :) height: %.1f <> parent-h: %d\n", hh, h); } #endif if (expand) { hh = h; } else { hh = rint(hh); } robwidget_set_area(rw, 0, 0, w, hh); } static void rob_vbox_child_pack(RobWidget *rw, RobWidget *chld, bool expand, bool fill) { rcontainer_child_pack(rw, chld, expand, fill); } static RobWidget * rob_vbox_new(bool homogeneous, int padding) { RobWidget * rw = robwidget_new(NULL); ROBWIDGET_SETNAME(rw, "vbox"); rw->self = (struct rob_container*) calloc(1, sizeof(struct rob_container)); ((struct rob_container*)rw->self)->homogeneous = homogeneous; ((struct rob_container*)rw->self)->padding = padding; ((struct rob_container*)rw->self)->expand = TRUE; rw->size_request = rvbox_size_request; rw->size_allocate = rvbox_size_allocate; rw->expose_event = rcontainer_expose_event; rw->mouseup = rcontainer_mouseup; rw->mousedown = rcontainer_mousedown; rw->mousemove = rcontainer_mousemove; rw->mousemove = rcontainer_mousemove; rw->mousescroll = rcontainer_mousescroll; rw->area.x=0; rw->area.y=0; rw->area.width = 0; rw->area.height = 0; return rw; } static void dump_tbl_req(struct rob_table *rt) { unsigned int x,y; printf("---REQ---\n"); printf("COLS: | "); for (x=0; x < rt->ncols; ++x) { printf(" *%4d* x %4d (%d,%d)|", rt->cols[x].req_w, rt->cols[x].req_h, rt->cols[x].is_expandable_x, rt->cols[x].is_expandable_y); } printf("\n---------------\n"); for (y=0; y < rt->nrows; ++y) { printf("ROW %d || %4d x *%4d* (%d,%d)\n", y, rt->rows[y].req_w, rt->rows[y].req_h, rt->rows[y].is_expandable_x, rt->rows[y].is_expandable_y); } } static void dump_tbl_acq(struct rob_table *rt) { unsigned int x,y; printf("---ALLOC---\n"); printf("COLS: | "); for (x=0; x < rt->ncols; ++x) { printf(" *%4d* x %4d |", rt->cols[x].acq_w, rt->cols[x].acq_h); } printf("\n---------------\n"); for (y=0; y < rt->nrows; ++y) { printf("ROW %d || %4d x *%4d*\n", y, rt->rows[y].acq_w, rt->rows[y].acq_h); } } static void rob_box_destroy(RobWidget * rw) { free(rw->self); robwidget_destroy(rw); } /* table layout [jach] */ static void rtable_size_request(RobWidget* rw, int *w, int *h) { assert(w && h); struct rob_table *rt = (struct rob_table*)rw->self; // reset for (unsigned int r=0; r < rt->nrows; ++r) { memset(&rt->rows[r], 0, sizeof(struct rob_table_field)); rt->rows[r].is_expandable_x = TRUE; rt->rows[r].is_expandable_y = TRUE; } for (unsigned int c=0; c < rt->ncols; ++c) { memset(&rt->cols[c], 0, sizeof(struct rob_table_field)); rt->cols[c].is_expandable_x = TRUE; rt->cols[c].is_expandable_y = TRUE; } // fill in childs for (unsigned int i=0; i < rt->nchilds; ++i) { int cw, ch; struct rob_table_child *tc = &rt->chld[i]; RobWidget * c = (RobWidget *) tc->rw; if (c->hidden) continue; c->size_request(c, &cw, &ch); #ifdef DEBUG_TABLE printf("widget %d wants (%d x %d) x-span:%d y-span: %d\n", i, cw, ch, (tc->right - tc->left), (tc->bottom - tc->top)); #endif int curw = 0, curh = 0; for (int span_x = tc->left; span_x < tc->right; ++span_x) { curw += rt->cols[span_x].req_w; } for (int span_y = tc->top; span_y < tc->bottom; ++span_y) { curh += rt->rows[span_y].req_h; } float avg_w = MAX(0, tc->xpadding * 2 + cw - curw) / (float)(tc->right - tc->left); float avg_h = MAX(0, tc->ypadding * 2 + ch - curh) / (float)(tc->bottom - tc->top); for (int span_x = tc->left; span_x < tc->right; ++span_x) { int tcw = rint (avg_w * (1 + span_x - tc->left)) - rint (avg_w * (span_x - tc->left)); rt->cols[span_x].req_w += tcw; rt->cols[span_x].req_h = MAX(rt->cols[span_x].req_h, ch); // unused -- homog if (!(tc->expand_x & RTK_EXPAND)) { rt->cols[span_x].is_expandable_x = FALSE; } } for (int span_y = tc->top; span_y < tc->bottom; ++span_y) { int tch = rint (avg_h * (1 + span_y - tc->top)) - rint (avg_h * (span_y - tc->top)); rt->rows[span_y].req_w = MAX(rt->rows[span_y].req_w, cw); // unused -- homog rt->rows[span_y].req_h += tch; if (!(tc->expand_y & RTK_EXPAND)) { rt->rows[span_y].is_expandable_y = FALSE; } } // reset initial size c->area.width = cw; c->area.height = ch; } // calc size of table int ww = 0; int hh = 0; for (unsigned int r=0; r < rt->nrows; ++r) { hh += rt->rows[r].req_h; } for (unsigned int c=0; c < rt->ncols; ++c) { ww += rt->cols[c].req_w; } #if 0 // homogeneous // set area of children to detected max for (unsigned int i=0; i < rt->nchilds; ++i) { int cw = 0; int ch = 0; struct rob_table_child *tc = &rt->chld[i]; RobWidget * c = (RobWidget *) tc->rw; if (c->hidden) continue; for (int span_x = tc->left; span_x < tc->right; ++span_x) { cw += rt->cols[span_x].req_w; //ch += rt->cols[span_x].req_h; } for (int span_y = tc->top; span_y < tc->bottom; ++span_y) { //cw += rt->rows[span_y].req_w; ch += rt->rows[span_y].req_h; } c->area.width = cw; c->area.height = ch; } #endif #ifdef DEBUG_TABLE dump_tbl_req(rt); #endif ww = ceil(ww); hh = ceil(hh); *w = ww; *h = hh; #ifdef DEBUG_TABLE printf("REQUEST TABLE SIZE: %d %d\n", ww, hh); #endif robwidget_set_area(rw, 0, 0, ww, hh); } static void rtable_size_allocate(RobWidget* rw, const int w, const int h) { struct rob_table *rt = (struct rob_table*)rw->self; #ifdef DEBUG_TABLE printf("table '%s' size_allocate %d, %d\n", ROBWIDGET_NAME(rw), w, h); #endif if (h < rw->area.height || w < rw->area.width) { printf(" !!! table size request error. want %.1fx%.1f got %dx%d\n", rw->area.width, rw->area.height, w, h); } if (h > rw->area.height) { int exp = 0; #ifdef DEBUG_TABLE printf("---TABLE CAN EXPAND in height to %d\n", h); #endif for (unsigned int r=0; r < rt->nrows; ++r) { if (rt->rows[r].req_h == 0) continue; if (rt->rows[r].is_expandable_y) { ++exp; } } if (exp > 0) { int cnt = 0; float xtra_height = (h - rw->area.height) / (float)exp; #ifdef DEBUG_TABLE printf("table expand %d rows by %.1f height\n", exp, xtra_height); #endif for (unsigned int r=0; r < rt->nrows; ++r) { if (rt->rows[r].req_h == 0) continue; if (!rt->rows[r].is_expandable_y) continue; rt->rows[r].expand = rint (xtra_height * (1 + cnt)) - rint (xtra_height * (cnt)); ++cnt; } } else { #ifdef DEBUG_TABLE printf("table no grow height\n"); #endif } } if (w > rw->area.width) { int exp = 0; #ifdef DEBUG_TABLE printf("TABLE CAN EXPAND in width to %d\n", w); #endif for (unsigned int c=0; c < rt->ncols; ++c) { if (rt->cols[c].req_w == 0) continue; if (rt->cols[c].is_expandable_x) { ++exp; } } if (exp > 0) { int cnt = 0; float xtra_width = (w - rw->area.width) / (float)exp; #ifdef DEBUG_TABLE printf("table expand %d columns by %.1f width\n", exp, xtra_width); #endif for (unsigned int c=0; c < rt->ncols; ++c) { if (rt->cols[c].req_w == 0) continue; if (!rt->cols[c].is_expandable_x) continue; rt->cols[c].expand = rint (xtra_width * (1 + cnt)) - rint (xtra_width * (cnt)); ++cnt; } } else { #ifdef DEBUG_TABLE printf("table no grow width\n"); #endif } } for (unsigned int c=0; c < rt->ncols; ++c) { rt->cols[c].acq_w = rt->cols[c].req_w + rt->cols[c].expand; } for (unsigned int r=0; r < rt->nrows; ++r) { rt->rows[r].acq_h = rt->rows[r].req_h + rt->rows[r].expand; } for (unsigned int i=0; i < rt->nchilds; ++i) { int cw = 0; int ch = 0; struct rob_table_child *tc = &rt->chld[i]; RobWidget * c = (RobWidget *) tc->rw; if (c->hidden) continue; c->size_request(c, &cw, &ch); #ifdef DEBUG_TABLE printf("widget %d wants (%d x %d) x-span:%d y-span: %d %d, %d\n", i, cw, ch, (tc->right - tc->left), (tc->bottom - tc->top), tc->left, tc->top); #endif int curw = 0, curh = 0; for (int span_x = tc->left; span_x < tc->right; ++span_x) { curw += rt->cols[span_x].acq_w; } for (int span_y = tc->top; span_y < tc->bottom; ++span_y) { curh += rt->rows[span_y].acq_h; } if (c->size_allocate) { int aw = curw - tc->xpadding * 2; int ah = curh - tc->ypadding * 2; if (tc->expand_x & RTK_FILL) cw = MAX(cw, aw); if (tc->expand_y & RTK_FILL) ch = MAX(ch, ah); c->size_allocate(c, cw, ch); cw = c->area.width; ch = c->area.height; } else { // shift the position of the item.. // XXX use acq rather than add expand? for (int tci = tc->left; tci < tc->right; ++tci) { cw += rt->cols[tci].expand; } for (int tri = tc->top; tri < tc->bottom; ++tri) { ch += rt->rows[tri].expand; } } #if 1 // verify layout if (cw + tc->xpadding * 2 > curw) { printf("TABLE child %d WIDTH %d > %d\n", i, cw, curw); } if (ch +tc->ypadding * 2 > curh) { printf("TABLE child %d HEIGHT %d > %d \n", i, ch, curh); } #endif #ifdef DEBUG_TABLE dump_tbl_acq(rt); printf("TABLECHILD %d '%s' use %.1fx%.1f (field: %dx%d)\n", i, ROBWIDGET_NAME(c), c->area.width, c->area.height, curw, curh); #endif } #ifdef DEBUG_TABLE dump_tbl_acq(rt); #endif int max_w = 0; int max_h = 0; /* set position after allocation */ for (unsigned int i=0; i < rt->nchilds; ++i) { int cw = 0; int ch = 0; int cx = 0; int cy = 0; struct rob_table_child *tc = &rt->chld[i]; RobWidget * c = (RobWidget *) tc->rw; if (c->hidden) continue; for (int span_x = tc->left; span_x < tc->right; ++span_x) { cw += rt->cols[span_x].acq_w; } for (int span_y = tc->top; span_y < tc->bottom; ++span_y) { ch += rt->rows[span_y].acq_h; } for (int span_x = 0; span_x < tc->left; ++span_x) { cx += rt->cols[span_x].acq_w; } for (int span_y = 0; span_y < tc->top; ++span_y) { cy += rt->rows[span_y].acq_h; } #ifdef DEBUG_TABLE printf("TABLECHILD %d avail %dx%d at %d+%d (wsize: %.1fx%.1f)\n", i, cw, ch, cx, cy, c->area.width, c->area.height); #endif if (tc->xpadding > 0) { if (cw < c->area.width + 2 * tc->xpadding) { printf("!!!! Table Padding:%d + cell %.0f < widget-width %d\n", tc->xpadding, c->area.width, cw); } } if (tc->ypadding > 0) { if (ch < c->area.height + 2 * tc->ypadding) { printf("!!!! Table Padding:%d + cell %.0f < widget-height %d\n", tc->ypadding, c->area.height, ch); } } cw -= tc->xpadding * 2; ch -= tc->ypadding * 2; if (c->position_set) { c->position_set(c, cw, ch); } else { robwidget_position_set(c, cw, ch); } c->area.x += cx + tc->xpadding; c->area.y += cy + tc->ypadding; if (c->area.x + c->area.width + tc->xpadding > max_w) max_w = c->area.x + c->area.width + tc->xpadding; if (c->area.y + c->area.height + tc->ypadding > max_h) max_h = c->area.y + c->area.height + tc->ypadding; #ifdef DEBUG_TABLE printf("TABLE %d packed to %.1f+%.1f %.1fx%.1f\n", i, c->area.x, c->area.y, c->area.width, c->area.height); #endif if (c->redraw_pending) { queue_draw(c); } } #ifdef DEBUG_TABLE printf("TABLE PACKED %.1f+%.1f %dx%d (given: %dx%d)\n", (w - max_w) / 2.0, (h - max_h) / 2.0, max_w, max_h, w, h); #endif if (max_w > w || max_h > h) { printf("TABLE OVERFLOW total %dx%d (given: %dx%d)\n", max_w, max_h, w, h); } else if (max_w < w || max_h < h) { // re-center content in case table did not fully expand const int xoff = floor((w - max_w) / 2.0); const int yoff = floor((h - max_h) / 2.0); #ifdef DEBUG_TABLE printf("RE-CENTER CHILDS by %dx%d %s\n", xoff, yoff, ROBWIDGET_NAME(rw)); #endif for (unsigned int i=0; i < rt->nchilds; ++i) { struct rob_table_child *tc = &rt->chld[i]; RobWidget * c = (RobWidget *) tc->rw; if (c->hidden) continue; c->area.x += xoff; c->area.y += yoff; } } robwidget_set_area(rw, 0, 0, w, h); } static void rob_table_attach(RobWidget *rw, RobWidget *chld, unsigned int left, unsigned int right, unsigned int top, unsigned int bottom, int xpadding, int ypadding, int xexpand, int yexpand ) { assert(left < right); assert(top < bottom); rcontainer_child_pack(rw, chld, (yexpand | xexpand) & RTK_FILL, /*unused*/ TRUE); struct rob_table *rt = (struct rob_table*)rw->self; if (right >= rt->ncols) { rob_table_resize (rt, rt->nrows, right); } if (bottom >= rt->nrows) { rob_table_resize (rt, bottom, rt->ncols); } rt->chld = (struct rob_table_child*) realloc(rt->chld, (rt->nchilds + 1) * sizeof(struct rob_table_child)); rt->chld[rt->nchilds].rw = chld; rt->chld[rt->nchilds].left = left; rt->chld[rt->nchilds].right = right; rt->chld[rt->nchilds].top = top; rt->chld[rt->nchilds].bottom = bottom; rt->chld[rt->nchilds].xpadding = xpadding; rt->chld[rt->nchilds].ypadding = ypadding; rt->chld[rt->nchilds].expand_x = xexpand; rt->chld[rt->nchilds].expand_y = yexpand; rt->nchilds++; } static void rob_table_attach_defaults(RobWidget *rw, RobWidget *chld, unsigned int left, unsigned int right, unsigned int top, unsigned int bottom) { rob_table_attach(rw, chld, left, right, top, bottom, 0, 0, RTK_EXANDF, RTK_EXANDF); } static RobWidget * rob_table_new(int rows, int cols, bool homogeneous) { RobWidget * rw = robwidget_new(NULL); ROBWIDGET_SETNAME(rw, "tbl"); rw->self = (struct rob_table*) calloc(1, sizeof(struct rob_table)); struct rob_table *rt = (struct rob_table*)rw->self; rt->homogeneous = homogeneous; rt->expand = TRUE; rob_table_resize (rt, rows, cols); rw->size_request = rtable_size_request; rw->size_allocate = rtable_size_allocate; rw->expose_event = rcontainer_expose_event; rw->mouseup = rcontainer_mouseup; rw->mousedown = rcontainer_mousedown; rw->mousemove = rcontainer_mousemove; rw->mousemove = rcontainer_mousemove; rw->mousescroll = rcontainer_mousescroll; rw->area.x=0; rw->area.y=0; rw->area.width = 0; rw->area.height = 0; return rw; } static void rob_table_destroy(RobWidget * rw) { struct rob_table *rt = (struct rob_table*)rw->self; free(rt->chld); free(rt->rows); free(rt->cols); free(rw->self); robwidget_destroy(rw); } /* recursive childpos cache */ static void rtoplevel_cache(RobWidget* rw, bool valid) { for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) { valid= FALSE; } rtoplevel_cache(c, valid); } robwidget_position_cache(rw); rw->cached_position = valid; } /* recursive ui-scale update */ static void rtoplevel_scale(RobWidget* rw, const float ws) { for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; rtoplevel_scale (c, ws); } rw->widget_scale = ws; } robtk-0.8.5/gl/posringbuf.h000066400000000000000000000046531463170413500156120ustar00rootroot00000000000000/* robtk LV2 GUI * * Copyright 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include #include /* simple lockless ringbuffer */ typedef struct { uint8_t *d; size_t rp; size_t wp; size_t len; } posringbuf; static posringbuf * posrb_alloc(size_t siz) { posringbuf *rb = (posringbuf*) malloc(sizeof(posringbuf)); rb->d = (uint8_t*) malloc(siz * sizeof(uint8_t)); rb->len = siz; rb->rp = 0; rb->wp = 0; return rb; } static void posrb_free(posringbuf *rb) { free(rb->d); free(rb); } static size_t posrb_write_space(posringbuf *rb) { if (rb->rp == rb->wp) return (rb->len -1); return ((rb->len + rb->rp - rb->wp) % rb->len) -1; } static size_t posrb_read_space(posringbuf *rb) { return ((rb->len + rb->wp - rb->rp) % rb->len); } static int posrb_read(posringbuf *rb, uint8_t *d, size_t len) { if (posrb_read_space(rb) < len) return -1; if (rb->rp + len <= rb->len) { memcpy((void*) d, (void*) &rb->d[rb->rp], len * sizeof (uint8_t)); } else { int part = rb->len - rb->rp; int remn = len - part; memcpy((void*) d, (void*) &(rb->d[rb->rp]), part * sizeof (uint8_t)); memcpy((void*) &(d[part]), (void*) rb->d, remn * sizeof (uint8_t)); } rb->rp = (rb->rp + len) % rb->len; return 0; } static int posrb_write(posringbuf *rb, uint8_t *d, size_t len) { if (posrb_write_space(rb) < len) return -1; if (rb->wp + len <= rb->len) { memcpy((void*) &rb->d[rb->wp], (void*) d, len * sizeof(uint8_t)); } else { int part = rb->len - rb->wp; int remn = len - part; memcpy((void*) &rb->d[rb->wp], (void*) d, part * sizeof(uint8_t)); memcpy((void*) rb->d, (void*) &d[part], remn * sizeof(uint8_t)); } rb->wp = (rb->wp + len) % rb->len; return 0; } static void posrb_read_clear(posringbuf *rb) { rb->rp = rb->wp; } robtk-0.8.5/gl/robwidget_gl.h000066400000000000000000000125641463170413500161040ustar00rootroot00000000000000/* robwidget - GL wrapper * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #define GET_HANDLE(HDL) (((RobWidget*)HDL)->self) #define robwidget_set_expose_event(RW, EVT) { (RW)->expose_event = EVT; } #define robwidget_set_size_request(RW, EVT) { (RW)->size_request = EVT; } #define robwidget_set_size_allocate(RW, EVT) { (RW)->size_allocate = EVT; } #define robwidget_set_size_limit(RW, EVT) { (RW)->size_limit = EVT; } #define robwidget_set_size_default(RW, EVT) { (RW)->size_default = EVT; } #define robwidget_set_mouseup(RW, EVT) { (RW)->mouseup = EVT; } #define robwidget_set_mousedown(RW, EVT) { (RW)->mousedown = EVT; } #define robwidget_set_mousemove(RW, EVT) { (RW)->mousemove = EVT; } #define robwidget_set_mousescroll(RW, EVT) { (RW)->mousescroll = EVT; } #define robwidget_set_enter_notify(RW, EVT) { (RW)->enter_notify = EVT; } #define robwidget_set_leave_notify(RW, EVT) { (RW)->leave_notify = EVT; } /* widget-tree & packing */ static void offset_traverse_parents(RobWidget *rw, RobTkBtnEvent *ev) { assert(rw); do { ev->x -= rw->area.x; ev->y -= rw->area.y; if (rw == rw->parent) { break; } rw = rw->parent; } while (rw); } static void offset_traverse_from_child(RobWidget *rw, RobTkBtnEvent *ev) { assert(rw); do { ev->x += rw->area.x; ev->y += rw->area.y; if (rw == rw->parent) { break; } rw = rw->parent; } while (rw); } static RobWidget * robwidget_child_at(RobWidget *rw, int x, int y) { for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; if (x >= c->area.x && y >= c->area.y && x <= c->area.x + c->area.width && y <= c->area.y + c->area.height ) { return c; } } return NULL; } static RobWidget* decend_into_widget_tree(RobWidget *rw, int x, int y) { if (rw->childcount == 0) return rw; x-=rw->area.x; y-=rw->area.y; for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; if (c->hidden) continue; if (c->block_events) continue; if (x >= c->area.x && y >= c->area.y && x <= c->area.x + c->area.width && y <= c->area.y + c->area.height ) { return decend_into_widget_tree(c, x, y); } } return NULL; } /*****************************************************************************/ /* RobWidget implementation */ /*declared in packer.h */ static void rtoplevel_size_request(RobWidget* rw, int *w, int *h); static bool rcontainer_expose_event(RobWidget* rw, cairo_t* cr, cairo_rectangle_t *ev); static RobWidget * robwidget_new(void *handle) { RobWidget * rw = (RobWidget *) calloc(1, sizeof(RobWidget)); rw->self = handle; rw->xalign = .5; rw->yalign = .5; rw->hidden = FALSE; rw->block_events = FALSE; rw->widget_scale = 1.0; return rw; } static void robwidget_make_toplevel(RobWidget *rw, void * const handle) { rw->top = handle; rw->parent = rw; } static void robwidget_destroy(RobWidget *rw) { if (!rw) return; #ifndef NDEBUG if (rw->children && rw->childcount == 0) fprintf(stderr, "robwidget_destroy: '%s' children <> childcount = 0\n", ROBWIDGET_NAME(rw)); if (!rw->children && rw->childcount != 0) fprintf(stderr, "robwidget_destroy: '%s' childcount <> children = NULL\n", ROBWIDGET_NAME(rw)); #endif #if 0 // recursive for (unsigned int i=0; i < rw->childcount; ++i) { RobWidget * c = (RobWidget *) rw->children[i]; robwidget_destroy(c); } #endif free(rw->children); #if 0 rw->children = NULL; rw->childcount = 0; #endif free(rw); } static void robwidget_set_size(RobWidget *rw, int w, int h) { rw->area.width = w; rw->area.height = h; } static void robwidget_set_area(RobWidget *rw, int x, int y, int w, int h) { rw->area.x = x; rw->area.y = y; rw->area.width = w; rw->area.height = h; } static void robwidget_set_alignment(RobWidget *rw, float xalign, float yalign) { rw->xalign = xalign; rw->yalign = yalign; } static void robwidget_resize_toplevel(RobWidget *rw, int w, int h) { resize_toplevel(rw, w, h); } static void robwidget_show(RobWidget *rw, bool resize_window) { // XXX never call from expose_event if (rw->hidden) { rw->hidden = FALSE; if (resize_window) resize_self(rw); else relayout_toplevel(rw); } } static void robwidget_hide(RobWidget *rw, bool resize_window) { // XXX never call from expose_event if (!rw->hidden) { rw->hidden = TRUE; if (resize_window) resize_self(rw); else relayout_toplevel(rw); } } /*****************************************************************************/ /* host helper */ static void const * robwidget_get_toplevel_handle(RobWidget *rw) { void const *h = NULL; while (rw) { if (rw == rw->parent) { h = rw->top; break; } rw = rw->parent; } return h; } robtk-0.8.5/gl/xternalui.c000066400000000000000000000017751463170413500154440ustar00rootroot00000000000000#ifdef XTERNAL_UI #ifdef USE_GUI_THREAD static int idle(LV2UI_Handle handle); #endif static int process_gui_events(LV2UI_Handle handle); static void x_run (struct lv2_external_ui * handle) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle->self; #ifdef USE_GUI_THREAD idle(self); #else process_gui_events(self); #endif if (self->close_ui && self->ui_closed) { self->close_ui = FALSE; #ifndef USE_GUI_THREAD ui_disable(self->ui); puglHideWindow(self->view); #else self->ui_queue_puglXWindow = -1; #endif self->ui_closed(self->controller); } } static void x_show (struct lv2_external_ui * handle) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle->self; #ifndef USE_GUI_THREAD puglShowWindow(self->view); ui_enable(self->ui); #else self->ui_queue_puglXWindow = 1; #endif } static void x_hide (struct lv2_external_ui * handle) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle->self; #ifndef USE_GUI_THREAD ui_disable(self->ui); puglHideWindow(self->view); #else self->ui_queue_puglXWindow = -1; #endif } #endif robtk-0.8.5/gl/xternalui.h000066400000000000000000000010371463170413500154400ustar00rootroot00000000000000#define LV2_EXTERNAL_UI_URI "http://lv2plug.in/ns/extensions/ui#external" #define LV2_EXTERNAL_UI_URI__KX__Host "http://kxstudio.sf.net/ns/lv2ext/external-ui#Host" /* API/ABI */ #ifdef __cplusplus extern "C" { #endif struct lv2_external_ui { void (* run)(struct lv2_external_ui * _this_); void (* show)(struct lv2_external_ui * _this_); void (* hide)(struct lv2_external_ui * _this_); void *self; }; struct lv2_external_ui_host { void (* ui_closed)(void* controller); const char * plugin_human_id; }; #ifdef __cplusplus } #endif robtk-0.8.5/gpg_check.c000066400000000000000000000060631463170413500147340ustar00rootroot00000000000000#ifdef WITH_SIGNATURE /* 'self' struct needs * bool gpg_verified; * char gpg_data[128]; */ #ifndef FALSE #define FALSE 0 #endif #ifndef TRUE #define TRUE -1 #endif { self->gpg_verified = FALSE; gp3_initialize (); load_master_key (); // in header WITH_SIGNATURE gp3_loglevel (GP3L_SILENT); int rc = -1; char signature_file0[1024] = ""; char signature_file1[1024] = ""; char signature_file2[1024] = ""; char signature_file3[1024] = ""; #ifdef SIGNOVERSION memset(self->gpg_data, 0, sizeof (self->gpg_data)); #else strcpy(self->gpg_data, "v" VERSION); #endif #ifdef _WIN32 ExpandEnvironmentStrings("%localappdata%\\"SIGFILE, signature_file0, 1024); ExpandEnvironmentStrings("%localappdata%\\x42_license.txt", signature_file2, 1024); const char * homedrive = getenv("HOMEDRIVE"); const char * homepath = getenv("HOMEPATH"); if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(SIGFILE) + 3) < 1024) { sprintf(signature_file1, "%s%s\\%s", homedrive, homepath, SIGFILE); } if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(SIGFILE) + 17) < 1024) { sprintf(signature_file3, "%s%s\\x42_license.txt", homedrive, homepath); } #else const char * home = getenv("HOME"); if (home && (strlen(home) + strlen(SIGFILE) + 3) < 1024) { sprintf(signature_file0, "%s/%s", home, SIGFILE); } if (home && (strlen(home) + strlen(SIGFILE) + 3) < 1024) { sprintf(signature_file1, "%s/.%s", home, SIGFILE); } if (home && (strlen(home) + 18) < 1024) { sprintf(signature_file2, "%s/x42_license.txt", home); } if (home && (strlen(home) + 18) < 1024) { sprintf(signature_file3, "%s/.x42_license.txt", home); } #endif if (testfile(signature_file0)) { rc = gp3_checksigfile (signature_file0); } else if (testfile(signature_file1)) { rc = gp3_checksigfile (signature_file1); } else if (testfile(signature_file2)) { rc = gp3_checksigfile (signature_file2); } else if (testfile(signature_file3)) { rc = gp3_checksigfile (signature_file3); } else { #if 0 fprintf(stderr, " *** no signature file found\n"); #endif } if (rc == 0) { char data[8192]; char *tmp=NULL; uint32_t len = gp3_get_text(data, sizeof(data)); if (len == sizeof(data)) data[sizeof(data)-1] = '\0'; else data[len] = '\0'; #if 0 fprintf(stderr, " *** signature:\n"); if (len > 0) fputs(data, stderr); #endif if ((tmp = strchr(data, '\n'))) *tmp = 0; self->gpg_data[sizeof(self->gpg_data) - 1] = 0; if (tmp++ && *tmp) { if ((tmp = strstr(tmp, RTK_URI))) { char *t1, *t2; self->gpg_verified = TRUE; t1 = tmp + strlen(RTK_URI); t2 = strchr(t1, '\n'); if (t2) { *t2 = 0; } if (strlen(t1) > 0 && strncmp(t1, VERSION, strlen(t1))) { self->gpg_verified = FALSE; } } } if (!self->gpg_verified) { #if 0 fprintf(stderr, " *** signature is not valid for this version/bundle.\n"); #endif } else { #ifndef SIGNOVERSION strncat(self->gpg_data, " ", sizeof(self->gpg_data) - strlen(self->gpg_data)); #endif strncat(self->gpg_data, data, sizeof(self->gpg_data) - strlen(self->gpg_data)); } } gp3_cleanup (); } #endif robtk-0.8.5/gpg_init.c000066400000000000000000000011051463170413500146120ustar00rootroot00000000000000#ifdef WITH_SIGNATURE // gpg sign tested releases #include "lv2_rgext.h" #ifdef _WIN32 # include #endif #include "gp3.h" #include #include /* test if file exists and is a regular file - returns 1 if ok */ static int testfile (const char *filename) { struct stat s; if (!filename || strlen(filename) < 1) return 0; int result= stat(filename, &s); if (result != 0) return 0; /* stat() failed */ if (S_ISREG(s.st_mode)) return 1; /* is a regular file - ok */ return 0; } struct license_info { char name[64]; char store[128]; }; #endif robtk-0.8.5/gpg_lv2ext.c000066400000000000000000000022121463170413500150730ustar00rootroot00000000000000struct _gpginfo { int gpg_verified; int gpg_checked; char gpg_data[128]; }; static struct _gpginfo gpginfo = {0, 0, }; // return -1 if no license is needed, 1 if licensed, 0 if not licensed static int is_licensed (LV2_Handle instance) { if (!gpginfo.gpg_checked) { gpginfo.gpg_checked = 1; #define self (&gpginfo) #define SIGNOVERSION #include "gpg_check.c" #undef self } return gpginfo.gpg_verified ? 1 : 0; } static char* licensee (LV2_Handle instance) { if (gpginfo.gpg_verified) { if (strlen (gpginfo.gpg_data) > 13 && !strncmp(gpginfo.gpg_data, "Licensed to ", 12)) { return strdup (&gpginfo.gpg_data[12]); } else { return strdup (gpginfo.gpg_data); } } return NULL; } static const char* product_uri (LV2_Handle instance) { return RTK_URI; } static const char* product_name (LV2_Handle instance) { return license_infos.name; } static const char* store_url (LV2_Handle instance) { return license_infos.store; } #define LV2_LICENSE_EXT_C \ static const LV2_License_Interface lif = { &is_licensed, &licensee, &product_uri, &product_name, &store_url }; \ if (!strcmp(uri, LV2_PLUGINLICENSE__interface)) { return &lif; } robtk-0.8.5/gtk2/000077500000000000000000000000001463170413500135205ustar00rootroot00000000000000robtk-0.8.5/gtk2/common_cgtk.h000066400000000000000000000053061463170413500161750ustar00rootroot00000000000000/* robwidget - gtk2 & GL wrapper * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #ifndef COMMON_CAIRO_H #define COMMON_CAIRO_H static PangoFontDescription * get_font_from_gtk () { PangoFontDescription * rv; GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); GtkWidget *foobar = gtk_label_new("Foobar"); gtk_container_add(GTK_CONTAINER(window), foobar); gtk_widget_ensure_style(foobar); PangoContext* pc = gtk_widget_get_pango_context(foobar); PangoFontDescription const * pfd = pango_context_get_font_description (pc); rv = pango_font_description_copy (pfd); gtk_widget_destroy(foobar); gtk_widget_destroy(window); assert(rv); return rv; } static void get_color_from_gtk (GdkColor *c, int which) { GtkWidget *window = gtk_window_new(GTK_WINDOW_TOPLEVEL); GtkWidget *foobar = gtk_label_new("Foobar"); gtk_container_add(GTK_CONTAINER(window), foobar); gtk_widget_ensure_style(foobar); GtkStyle *style = gtk_widget_get_style(foobar); switch (which) { default: memcpy((void*) c, (void*) &style->fg[GTK_STATE_NORMAL], sizeof(GdkColor)); break; case 1: memcpy((void*) c, (void*) &style->bg[GTK_STATE_NORMAL], sizeof(GdkColor)); break; case 2: memcpy((void*) c, (void*) &style->fg[GTK_STATE_ACTIVE], sizeof(GdkColor)); break; } gtk_widget_destroy(foobar); gtk_widget_destroy(window); } static float robtk_colorcache[3][4]; static bool robtk_colorcached[3] = { false, false, false }; static void get_color_from_theme (int which, float *col) { GdkColor color; assert(which >= 0 && which <= 2); if (robtk_colorcached[which]) { memcpy(col, robtk_colorcache[which], 4 * sizeof(float)); } else { get_color_from_gtk(&color, which); col[0] = color.red/65536.0; col[1] = color.green/65536.0; col[2] = color.blue/65536.0; col[3] = 1.0; memcpy(robtk_colorcache[which], col, 4 * sizeof(float)); robtk_colorcached[which] = true; } } static PangoFontDescription * get_font_from_theme () { return get_font_from_gtk(); // TODO cache this -- but how to free on exit? } #endif robtk-0.8.5/gtk2/robwidget_gtk.h000066400000000000000000000231601463170413500165260ustar00rootroot00000000000000/* robwidget - gtk2 & GL wrapper * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ /* GTK PROXY FUNCTIONS */ static gboolean robtk_expose_event(GtkWidget *w, GdkEventExpose *ev, gpointer handle) { RobWidget* self = (RobWidget*)handle; cairo_t* cr = gdk_cairo_create(GDK_DRAWABLE(w->window)); cairo_rectangle_t ea; ea.x = ev->area.x; ea.width = ev->area.width; ea.y = ev->area.y; ea.height = ev->area.height; self->expose_event(self, cr, &ea); cairo_destroy (cr); return TRUE; } static gboolean robtk_mousedown(GtkWidget *w, GdkEventButton *ev, gpointer handle) { RobWidget* self = (RobWidget*)handle; RobTkBtnEvent event; event.x = ev->x; event.y = ev->y; event.state = ev->state; event.direction = ROBTK_SCROLL_ZERO; event.button = ev->button; if (self->mousedown(self, &event)) return TRUE; return FALSE; } static gboolean robtk_mouseup(GtkWidget *w, GdkEventButton *ev, gpointer handle) { RobWidget* self = (RobWidget*)handle; RobTkBtnEvent event; event.x = ev->x; event.y = ev->y; event.state = ev->state; event.direction = ROBTK_SCROLL_ZERO; event.button = ev->button; if (self->mouseup(self, &event)) return TRUE; return FALSE; } static gboolean robtk_mousemove(GtkWidget *w, GdkEventMotion *ev, gpointer handle) { RobWidget* self = (RobWidget*)handle; RobTkBtnEvent event; event.x = ev->x; event.y = ev->y; event.state = ev->state; event.button = -1; event.direction = ROBTK_SCROLL_ZERO; if (self->mousemove(self, &event)) return TRUE; return FALSE; } static gboolean robtk_mousescroll(GtkWidget *w, GdkEventScroll *ev, gpointer handle) { RobWidget* self = (RobWidget*)handle; RobTkBtnEvent event; event.x = ev->x; event.y = ev->y; event.state = 0; event.button = -1; switch (ev->direction) { case GDK_SCROLL_UP: event.direction = ROBTK_SCROLL_UP; break; case GDK_SCROLL_DOWN: event.direction = ROBTK_SCROLL_DOWN; break; case GDK_SCROLL_LEFT: event.direction = ROBTK_SCROLL_LEFT; break; case GDK_SCROLL_RIGHT: event.direction = ROBTK_SCROLL_RIGHT; break; default: event.direction = ROBTK_SCROLL_ZERO; break; } if (self->mousescroll(self, &event)) return TRUE; return FALSE; } static void robtk_enter_notify(GtkWidget *w, GdkEvent *event, gpointer handle) { RobWidget* self = (RobWidget*)handle; if (self->enter_notify) { self->enter_notify(self); } } static void robtk_leave_notify(GtkWidget *w, GdkEvent *event, gpointer handle) { RobWidget* self = (RobWidget*)handle; if (self->leave_notify) { self->leave_notify(self); } } static void robtk_size_request(GtkWidget *w, GtkRequisition *r, gpointer handle) { RobWidget* self = (RobWidget*)handle; int x,y; x = r->width; y = r->height; self->size_request(self, &x, &y); r->width = x; r->height = y; } static void robtk_size_allocate(GtkWidget *w, GdkRectangle *r, gpointer handle) { RobWidget* self = (RobWidget*)handle; self->size_allocate(self, r->width, r->height); //if (self->c) { gtk_widget_set_size_request(self->c, r->width, r->height); } } /***********************************************************************/ #define GET_HANDLE(HDL) (((RobWidget*)HDL)->self) #define robwidget_set_expose_event(RW, EVT) { \ (RW)->expose_event=EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "expose_event", G_CALLBACK (robtk_expose_event), (RW)); \ } #define robwidget_set_size_request(RW, EVT) { \ int w,h; \ EVT(RW, &w, &h); \ (RW)->size_request = EVT; \ gtk_drawing_area_size(GTK_DRAWING_AREA(RW->m0), w, h); \ g_signal_connect (G_OBJECT ((RW)->c), "size-request", G_CALLBACK (robtk_size_request), (RW)); \ } #define robwidget_set_size_allocate(RW, EVT) { \ (RW)->size_allocate = EVT; \ g_signal_connect (G_OBJECT ((RW)->c), "size-allocate", G_CALLBACK (robtk_size_allocate), (RW)); \ } #define robwidget_set_size_limit(RW, EVT) { (RW)->size_limit = EVT; } #define robwidget_set_size_default(RW, EVT) { (RW)->size_default = EVT; } #define robwidget_set_mousedown(RW, EVT) { \ gtk_widget_add_events((RW)->m0, GDK_BUTTON_PRESS_MASK); \ (RW)->mousedown=EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "button-press-event", G_CALLBACK (robtk_mousedown), (RW)); \ } #define robwidget_set_mouseup(RW, EVT) { \ gtk_widget_add_events((RW)->m0, GDK_BUTTON_RELEASE_MASK | GDK_BUTTON_PRESS_MASK); \ (RW)->mouseup=EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "button-release-event", G_CALLBACK (robtk_mouseup), (RW)); \ } #define robwidget_set_mousemove(RW, EVT) { \ gtk_widget_add_events((RW)->m0, GDK_BUTTON1_MOTION_MASK | GDK_POINTER_MOTION_MASK); \ (RW)->mousemove=EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "motion-notify-event", G_CALLBACK (robtk_mousemove), (RW)); \ } #define robwidget_set_mousescroll(RW, EVT) { \ gtk_widget_add_events((RW)->m0, GDK_SCROLL_MASK); \ (RW)->mousescroll=EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "scroll-event", G_CALLBACK (robtk_mousescroll), (RW)); \ } #define robwidget_set_enter_notify(RW, EVT) { \ gtk_widget_add_events((RW)->m0, GDK_ENTER_NOTIFY_MASK); \ (RW)->enter_notify = EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "enter-notify-event", G_CALLBACK (robtk_enter_notify), (RW)); \ } #define robwidget_set_leave_notify(RW, EVT) { \ gtk_widget_add_events((RW)->m0, GDK_LEAVE_NOTIFY_MASK); \ (RW)->leave_notify = EVT; \ g_signal_connect (G_OBJECT ((RW)->m0), "leave-notify-event", G_CALLBACK (robtk_leave_notify), (RW)); \ } /*************************************************************/ static RobWidget * robwidget_new(void *handle) { RobWidget * rw = (RobWidget *) calloc(1, sizeof(RobWidget)); rw->self = handle; rw->m0 = gtk_drawing_area_new(); rw->c = gtk_alignment_new(0, .5, 0, 0); rw->widget_scale = 1.0; gtk_container_add(GTK_CONTAINER(rw->c), rw->m0); gtk_widget_set_redraw_on_allocate(rw->m0, TRUE); return rw; } static void robwidget_make_toplevel(RobWidget *rw, void * const handle) { ; } static void robwidget_set_area(RobWidget *rw, int x, int y, int w, int h) { ; } static void robwidget_destroy(RobWidget *rw) { if (rw->m0) gtk_widget_destroy(rw->m0); if (rw->c) gtk_widget_destroy(rw->c); free(rw); } static void robwidget_set_size(RobWidget *rw, int w, int h) { gtk_widget_set_size_request(rw->m0, w, h); } static void robwidget_set_alignment(RobWidget *rw, float xalign, float yalign) { gtk_alignment_set(GTK_ALIGNMENT(rw->c), xalign, yalign, 0, 0); rw->xalign = xalign; rw->yalign = yalign; } static void robwidget_resize_toplevel(RobWidget *rw, int w, int h) { GtkWidget *tlw = gtk_widget_get_toplevel(rw->c); if (tlw) { gtk_window_resize (GTK_WINDOW(tlw), w, h); } } static void robwidget_show(RobWidget *rw, bool resize_window) { gtk_widget_show_all(rw->c); } static void robwidget_hide(RobWidget *rw, bool resize_window) { if (!resize_window) { gtk_widget_hide(rw->c); return; } #ifdef USE_GTK_RESIZE_HACK gint ww,wh; GtkWidget *tlw = gtk_widget_get_toplevel(rw->c); if (tlw) { gtk_window_get_size(GTK_WINDOW(tlw), &ww, &wh); } gtk_widget_hide(rw->c); if (tlw) { gtk_window_resize (GTK_WINDOW(tlw), ww, 100); } #else gtk_widget_hide(rw->c); #endif } /* HOST PROVIDED FUNCTIONS */ static void queue_draw(RobWidget *h) { if (h->m0) gtk_widget_queue_draw(h->m0); else gtk_widget_queue_draw(h->c); // cb_preferences's ui->box } static void queue_draw_area(RobWidget *rw, int x, int y, int w, int h) { GdkRectangle rect; if (!rw->m0->window) return; rect.x=x; rect.y=y; rect.width=w; rect.height=h; GdkRegion *region = gdk_region_rectangle (&rect); gdk_window_invalidate_region (rw->m0->window, region, true); gdk_region_destroy(region); } static void queue_tiny_area(RobWidget *rw, float x, float y, float w, float h) { queue_draw_area(rw, x, y, w, h); } static RobWidget* rob_hbox_new(gboolean homogeneous, gint padding) { RobWidget * rw = (RobWidget *) calloc(1, sizeof(RobWidget)); rw->c = gtk_hbox_new(homogeneous, padding); return rw; } static RobWidget* rob_vbox_new(gboolean homogeneous, gint padding) { RobWidget * rw = (RobWidget *) calloc(1, sizeof(RobWidget)); rw->c = gtk_vbox_new(homogeneous, padding); return rw; } static RobWidget* rob_table_new(guint rows, guint cols, gboolean homogeneous) { RobWidget * rw = (RobWidget *) calloc(1, sizeof(RobWidget)); rw->c = gtk_table_new(rows, cols, homogeneous); return rw; } #define RTK_SHRINK GTK_SHRINK #define RTK_EXPAND GTK_EXPAND #define RTK_FILL GTK_FILL #define RTK_EXANDF (GTK_EXPAND|GTK_FILL) #define rob_hbox_child_pack(BX,CLD,EXP,FILL) gtk_box_pack_start(GTK_BOX((BX)->c), (CLD)->c, EXP, FILL, 0) #define rob_vbox_child_pack(BX,CLD,EXP,FILL) gtk_box_pack_start(GTK_BOX((BX)->c), (CLD)->c, EXP, FILL, 0) #define rob_table_attach(RW, CL, A1, A2, A3, A4, A5, A6, A7, A8) \ gtk_table_attach(GTK_TABLE((RW)->c), (CL)->c, A1, A2, A3, A4 \ , (GtkAttachOptions)(A7) \ , (GtkAttachOptions)(A8) \ , A5, A6) #define rob_table_attach_defaults(RW, CL, A1, A2, A3, A4) \ gtk_table_attach_defaults(GTK_TABLE((RW)->c), (CL)->c, A1, A2, A3, A4) #define rob_table_destroy(RW) { \ gtk_widget_destroy((RW)->c); \ free(RW); \ } #define rob_box_destroy(RW) { \ gtk_widget_destroy((RW)->c); \ free(RW); \ } robtk-0.8.5/jackwrap.c000066400000000000000000001574101463170413500146270ustar00rootroot00000000000000/* x42 jack wrapper / minimal LV2 host * * Copyright (C) 2012-2019 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef UPDATE_FREQ_RATIO #define UPDATE_FREQ_RATIO 60 // MAX # of audio-cycles per GUI-refresh #endif #ifndef JACK_AUTOCONNECT #define JACK_AUTOCONNECT 0 #endif #ifndef UI_UPDATE_FPS #define UI_UPDATE_FPS 25 #endif #ifndef MAXDELAY #define MAXDELAY 192001 // delayline max possible delay #endif #ifndef MAXPERIOD #define MAXPERIOD 8192 // delayline - max period size (jack-period) #endif /////////////////////////////////////////////////////////////////////////////// #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifdef WIN32 #include #include #define pthread_t //< override jack.h def #endif #ifdef __APPLE__ #include extern void rtk_osx_api_init (void); extern void rtk_osx_api_terminate (void); extern void rtk_osx_api_run (void); extern void rtk_osx_api_err (const char* msg); #endif #include #include #include #include #include #include #include #include #include #include #if (defined _WIN32 && defined RTK_STATIC_INIT) #include #endif #ifndef _WIN32 #include #else #include #endif #ifdef USE_WEAK_JACK #include "weakjack/weak_libjack.h" #else #include #include #include #endif #undef pthread_t #ifdef HAVE_LV2_1_18_6 #include #include #include #include #include #include #include #include #include #else #include #include #include #include #include #include #include #include #include #endif #include "./gl/xternalui.h" #ifndef WIN32 #include #include #endif #define LV2_EXTERNAL_UI_RUN(ptr) (ptr)->run (ptr) #define LV2_EXTERNAL_UI_SHOW(ptr) (ptr)->show (ptr) #define LV2_EXTERNAL_UI_HIDE(ptr) (ptr)->hide (ptr) #define nan NAN #ifndef UINT32_MAX #define UINT32_MAX (4294967295U) #endif static const LV2_Descriptor* plugin_dsp; static const LV2UI_Descriptor* plugin_gui; static LV2_Handle plugin_instance = NULL; static LV2UI_Handle gui_instance = NULL; static float* plugin_ports_pre = NULL; static float* plugin_ports_post = NULL; static LV2_Atom_Sequence* atom_in = NULL; static LV2_Atom_Sequence* atom_out = NULL; static jack_port_t** input_port = NULL; static jack_port_t** output_port = NULL; static jack_port_t* midi_in = NULL; static jack_port_t* midi_out = NULL; static jack_client_t* j_client = NULL; static uint32_t j_samplerate = 48000; static int _freewheeling = 0; struct transport_position { jack_nframes_t position; float bpm; bool rolling; } j_transport = { 0, 0, false }; static jack_ringbuffer_t* rb_ctrl_to_ui = NULL; static jack_ringbuffer_t* rb_ctrl_from_ui = NULL; static jack_ringbuffer_t* rb_atom_to_ui = NULL; static jack_ringbuffer_t* rb_atom_from_ui = NULL; #ifdef HAVE_LIBLO #include lo_server_thread osc_server = NULL; static jack_ringbuffer_t* rb_osc_to_ui = NULL; typedef struct _osc_midi_event { size_t size; uint8_t buffer[3]; } osc_midi_event_t; #ifndef OSC_MIDI_QUEUE_SIZE #define OSC_MIDI_QUEUE_SIZE (256) #endif static osc_midi_event_t event_queue[OSC_MIDI_QUEUE_SIZE]; static int queued_events_start = 0; static int queued_events_end = 0; #endif static pthread_mutex_t gui_thread_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER; static uint32_t uri_midi_MidiEvent = 0; static uint32_t uri_atom_Sequence = 0; static uint32_t uri_atom_EventTransfer = 0; static uint32_t uri_time_Position = 0; static uint32_t uri_time_frame = 0; static uint32_t uri_time_speed = 0; static uint32_t uri_time_bar = 0; static uint32_t uri_time_barBeat = 0; static uint32_t uri_time_beatUnit = 0; static uint32_t uri_time_beatsPerBar = 0; static uint32_t uri_time_beatsPerMinute = 0; static char** urimap = NULL; static uint32_t urimap_len = 0; enum PortType { CONTROL_IN = 0, CONTROL_OUT, AUDIO_IN, AUDIO_OUT, MIDI_IN, MIDI_OUT, ATOM_IN, ATOM_OUT }; struct DelayBuffer { jack_latency_range_t port_latency; int wanted_delay; int c_dly; // current delay int w_ptr; int r_ptr; float out_buffer[MAXPERIOD]; float delay_buffer[MAXDELAY]; }; struct PValue { uint32_t port_idx; float value; }; struct LV2Port { const char* name; enum PortType porttype; float val_default; float val_min; float val_max; const char* doc; }; typedef struct _RtkLv2Description { const LV2_Descriptor* (*lv2_descriptor) (uint32_t index); const LV2UI_Descriptor* (*lv2ui_descriptor) (uint32_t index); const uint32_t dsp_descriptor_id; const uint32_t gui_descriptor_id; const char* plugin_human_id; const struct LV2Port* ports; const uint32_t nports_total; const uint32_t nports_audio_in; const uint32_t nports_audio_out; const uint32_t nports_midi_in; const uint32_t nports_midi_out; const uint32_t nports_atom_in; const uint32_t nports_atom_out; const uint32_t nports_ctrl; const uint32_t nports_ctrl_in; const uint32_t nports_ctrl_out; const uint32_t min_atom_bufsiz; const bool send_time_info; const uint32_t latency_ctrl_port; } RtkLv2Description; static RtkLv2Description const* inst; /* a simple state machine for this client */ static volatile enum { Run, Exit } client_state = Run; static struct lv2_external_ui_host extui_host; static struct lv2_external_ui* extui = NULL; static LV2UI_Controller controller = NULL; static LV2_Atom_Forge lv2_forge; static uint32_t* portmap_a_in; static uint32_t* portmap_a_out; static uint32_t* portmap_rctl; static uint32_t* portmap_ctrl; static uint32_t portmap_atom_to_ui = -1; static uint32_t portmap_atom_from_ui = -1; static uint32_t uri_to_id (LV2_URID_Map_Handle handle, const char* uri); static jack_ringbuffer_t* worker_requests = NULL; static jack_ringbuffer_t* worker_responses = NULL; static pthread_t worker_thread; static pthread_mutex_t worker_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t worker_ready = PTHREAD_COND_INITIALIZER; static LV2_Worker_Interface* worker_iface = NULL; static pthread_mutex_t port_write_lock = PTHREAD_MUTEX_INITIALIZER; static struct DelayBuffer** delayline = NULL; static uint32_t worst_capture_latency = 0; static uint32_t plugin_latency = 0; /****************************************************************************** * Delayline for latency compensation */ #define FADE_LEN (16) #define INCREMENT_PTRS \ dly->r_ptr = (dly->r_ptr + 1) % MAXDELAY; \ dly->w_ptr = (dly->w_ptr + 1) % MAXDELAY; static float* delay_port (struct DelayBuffer* dly, uint32_t n_samples, float* in) { uint32_t pos = 0; const int delay = dly->wanted_delay; const float* const input = in; float* const output = dly->out_buffer; if (dly->c_dly == delay && delay == 0) { // only copy data into buffer in case delay time changes for (; pos < n_samples; pos++) { dly->delay_buffer[dly->w_ptr] = input[pos]; INCREMENT_PTRS; } return in; } // fade if delaytime changes if (dly->c_dly != delay) { const uint32_t fade_len = (n_samples >= FADE_LEN) ? FADE_LEN : n_samples / 2; // fade out for (; pos < fade_len; pos++) { const float gain = (float)(fade_len - pos) / (float)fade_len; dly->delay_buffer[dly->w_ptr] = input[pos]; output[pos] = dly->delay_buffer[dly->r_ptr] * gain; INCREMENT_PTRS; } // update read pointer dly->r_ptr += dly->c_dly - delay; if (dly->r_ptr < 0) { dly->r_ptr -= MAXDELAY * floor (dly->r_ptr / (float)MAXDELAY); } dly->r_ptr = dly->r_ptr % MAXDELAY; dly->c_dly = delay; // fade in for (; pos < 2 * fade_len; pos++) { const float gain = (float)(pos - fade_len) / (float)fade_len; dly->delay_buffer[dly->w_ptr] = input[pos]; output[pos] = dly->delay_buffer[dly->r_ptr] * gain; INCREMENT_PTRS; } } for (; pos < n_samples; pos++) { dly->delay_buffer[dly->w_ptr] = input[pos]; output[pos] = dly->delay_buffer[dly->r_ptr]; INCREMENT_PTRS; } return dly->out_buffer; } /////////////////////////// // GET INFO FROM LV2 TTL // // see lv2ttl2c // // define _plugin // /////////////////////////// #include JACK_DESCRIPT //// /////////////////////////// /****************************************************************************** * JACK */ static int process (jack_nframes_t nframes, void* arg) { if (nframes > MAXPERIOD) { static bool warned_max_period = false; if (!warned_max_period) { warned_max_period = true; fprintf (stderr, "Jack Period Size > %d is not supported (current %d)\n", MAXPERIOD, nframes); } if (inst->nports_midi_out > 0) { void* buf = jack_port_get_buffer (midi_out, nframes); jack_midi_clear_buffer (buf); } for (uint32_t i = 0; i < inst->nports_audio_out; ++i) { float* bp = (float*)jack_port_get_buffer (output_port[i], nframes); memset (bp, 0, nframes * sizeof (float)); } return 0; } while (jack_ringbuffer_read_space (rb_ctrl_from_ui) >= sizeof (uint32_t) + sizeof (float)) { uint32_t idx; jack_ringbuffer_read (rb_ctrl_from_ui, (char*)&idx, sizeof (uint32_t)); jack_ringbuffer_read (rb_ctrl_from_ui, (char*)&(plugin_ports_pre[idx]), sizeof (float)); } /* Get Jack transport position */ jack_position_t pos; const bool rolling = (jack_transport_query (j_client, &pos) == JackTransportRolling); const bool transport_changed = (rolling != j_transport.rolling || pos.frame != j_transport.position || ((pos.valid & JackPositionBBT) && (pos.beats_per_minute != j_transport.bpm))); /* atom buffers */ if (inst->nports_atom_in > 0 || inst->nports_midi_in > 0) { /* start Atom sequence */ atom_in->atom.type = uri_atom_Sequence; atom_in->atom.size = 8; LV2_Atom_Sequence_Body* body = &atom_in->body; body->unit = 0; // URID of unit of event time stamp LV2_ATOM__timeUnit ?? body->pad = 0; // unused uint8_t* seq = (uint8_t*)(body + 1); if (transport_changed && inst->send_time_info) { uint8_t pos_buf[256]; LV2_Atom* lv2_pos = (LV2_Atom*)pos_buf; lv2_atom_forge_set_buffer (&lv2_forge, pos_buf, sizeof (pos_buf)); LV2_Atom_Forge* forge = &lv2_forge; LV2_Atom_Forge_Frame frame; #ifdef HAVE_LV2_1_8 lv2_atom_forge_object (&lv2_forge, &frame, 1, uri_time_Position); #else lv2_atom_forge_blank (&lv2_forge, &frame, 1, uri_time_Position); #endif lv2_atom_forge_property_head (forge, uri_time_frame, 0); lv2_atom_forge_long (forge, pos.frame); lv2_atom_forge_property_head (forge, uri_time_speed, 0); lv2_atom_forge_float (forge, rolling ? 1.0 : 0.0); if (pos.valid & JackPositionBBT) { lv2_atom_forge_property_head (forge, uri_time_barBeat, 0); lv2_atom_forge_float (forge, pos.beat - 1 + (pos.tick / pos.ticks_per_beat)); lv2_atom_forge_property_head (forge, uri_time_bar, 0); lv2_atom_forge_long (forge, pos.bar - 1); lv2_atom_forge_property_head (forge, uri_time_beatUnit, 0); lv2_atom_forge_int (forge, pos.beat_type); lv2_atom_forge_property_head (forge, uri_time_beatsPerBar, 0); lv2_atom_forge_float (forge, pos.beats_per_bar); lv2_atom_forge_property_head (forge, uri_time_beatsPerMinute, 0); lv2_atom_forge_float (forge, pos.beats_per_minute); } uint32_t size = lv2_pos->size; uint32_t padded_size = ((sizeof (LV2_Atom_Event) + size) + 7) & (~7); if (inst->min_atom_bufsiz > padded_size) { //printf("send time..\n"); LV2_Atom_Event* aev = (LV2_Atom_Event*)seq; aev->time.frames = 0; aev->body.size = size; aev->body.type = lv2_pos->type; memcpy (LV2_ATOM_BODY (&aev->body), LV2_ATOM_BODY (lv2_pos), size); atom_in->atom.size += padded_size; seq += padded_size; } } if (gui_instance) { while (jack_ringbuffer_read_space (rb_atom_from_ui) > sizeof (LV2_Atom)) { LV2_Atom a; jack_ringbuffer_read (rb_atom_from_ui, (char*)&a, sizeof (LV2_Atom)); uint32_t padded_size = atom_in->atom.size + a.size + sizeof (int64_t); if (inst->min_atom_bufsiz > padded_size) { memset (seq, 0, sizeof (int64_t)); // LV2_Atom_Event->time seq += sizeof (int64_t); jack_ringbuffer_read (rb_atom_from_ui, (char*)seq, a.size); seq += a.size; atom_in->atom.size += a.size + sizeof (int64_t); } } } if (inst->nports_midi_in > 0) { #ifdef HAVE_LIBLO /*inject OSC midi events, use time 0 */ while (queued_events_end != queued_events_start) { uint32_t size = event_queue[queued_events_end].size; uint32_t padded_size = ((sizeof (LV2_Atom_Event) + size) + 7) & (~7); if (inst->min_atom_bufsiz > padded_size) { LV2_Atom_Event* aev = (LV2_Atom_Event*)seq; aev->time.frames = 0; // time aev->body.size = size; aev->body.type = uri_midi_MidiEvent; memcpy (LV2_ATOM_BODY (&aev->body), event_queue[queued_events_end].buffer, size); atom_in->atom.size += padded_size; seq += padded_size; } queued_events_end = (queued_events_end + 1) % OSC_MIDI_QUEUE_SIZE; } #endif /* inject jack midi events */ void* buf = jack_port_get_buffer (midi_in, nframes); for (uint32_t i = 0; i < jack_midi_get_event_count (buf); ++i) { jack_midi_event_t ev; jack_midi_event_get (&ev, buf, i); uint32_t size = ev.size; uint32_t padded_size = ((sizeof (LV2_Atom_Event) + size) + 7) & (~7); if (inst->min_atom_bufsiz > padded_size) { LV2_Atom_Event* aev = (LV2_Atom_Event*)seq; aev->time.frames = ev.time; aev->body.size = size; aev->body.type = uri_midi_MidiEvent; memcpy (LV2_ATOM_BODY (&aev->body), ev.buffer, size); atom_in->atom.size += padded_size; seq += padded_size; } } } } if (inst->nports_atom_out > 0 || inst->nports_midi_out > 0) { atom_out->atom.type = 0; atom_out->atom.size = inst->min_atom_bufsiz; } /* make a backup copy, to see what was changed */ memcpy (plugin_ports_post, plugin_ports_pre, inst->nports_ctrl * sizeof (float)); /* expected transport state in next cycle */ j_transport.position = rolling ? pos.frame + nframes : pos.frame; j_transport.bpm = pos.beats_per_minute; j_transport.rolling = rolling; /* [re] connect jack audio buffers */ for (uint32_t i = 0; i < inst->nports_audio_out; i++) { plugin_dsp->connect_port (plugin_instance, portmap_a_out[i], jack_port_get_buffer (output_port[i], nframes)); } for (uint32_t i = 0; i < inst->nports_audio_in; i++) { delayline[i]->wanted_delay = worst_capture_latency - delayline[i]->port_latency.max; plugin_dsp->connect_port ( plugin_instance, portmap_a_in[i], delay_port (delayline[i], nframes, (float*)jack_port_get_buffer (input_port[i], nframes))); } /* run the plugin */ plugin_dsp->run (plugin_instance, nframes); /* handle worker emit response - may amend Atom seq... */ if (worker_responses) { uint32_t read_space = jack_ringbuffer_read_space (worker_responses); while (read_space) { uint32_t size = 0; char worker_response[4096]; jack_ringbuffer_read (worker_responses, (char*)&size, sizeof (size)); jack_ringbuffer_read (worker_responses, worker_response, size); worker_iface->work_response (plugin_instance, size, worker_response); read_space -= sizeof (size) + size; } } /* create port-events for change values */ if (gui_instance) { for (uint32_t p = 0; p < inst->nports_ctrl; p++) { if (inst->ports[portmap_rctl[p]].porttype != CONTROL_OUT) continue; if (plugin_ports_pre[p] != plugin_ports_post[p]) { if (inst->latency_ctrl_port != UINT32_MAX && p == portmap_ctrl[inst->latency_ctrl_port]) { plugin_latency = rintf (plugin_ports_pre[p]); // TODO handle case if there's no GUI thread to call // jack_recompute_total_latencies() } if (jack_ringbuffer_write_space (rb_ctrl_to_ui) >= sizeof (uint32_t) + sizeof (float)) { jack_ringbuffer_write (rb_ctrl_to_ui, (char*)&portmap_rctl[p], sizeof (uint32_t)); jack_ringbuffer_write (rb_ctrl_to_ui, (char*)&plugin_ports_pre[p], sizeof (float)); } } } } if (inst->nports_midi_out > 0) { void* buf = jack_port_get_buffer (midi_out, nframes); jack_midi_clear_buffer (buf); } /* Atom sequence port-events */ if (inst->nports_atom_out + inst->nports_midi_out > 0 && atom_out->atom.size > sizeof (LV2_Atom)) { if (gui_instance && jack_ringbuffer_write_space (rb_atom_to_ui) >= atom_out->atom.size + 2 * sizeof (LV2_Atom)) { LV2_Atom a = { atom_out->atom.size + (uint32_t)sizeof (LV2_Atom), 0 }; jack_ringbuffer_write (rb_atom_to_ui, (char*)&a, sizeof (LV2_Atom)); jack_ringbuffer_write (rb_atom_to_ui, (char*)atom_out, a.size); } if (inst->nports_midi_out) { void* buf = jack_port_get_buffer (midi_out, nframes); LV2_Atom_Event const* ev = (LV2_Atom_Event const*)((&(atom_out)->body) + 1); // lv2_atom_sequence_begin while ((const uint8_t*)ev < ((const uint8_t*)&(atom_out)->body + (atom_out)->atom.size)) { if (ev->body.type == uri_midi_MidiEvent) { jack_midi_event_write (buf, ev->time.frames, (const uint8_t*)(ev + 1), ev->body.size); } ev = (LV2_Atom_Event const*)/* lv2_atom_sequence_next() */ ((const uint8_t*)ev + sizeof (LV2_Atom_Event) + ((ev->body.size + 7) & ~7)); } } } /* signal worker end of process run */ if (worker_iface && worker_iface->end_run) { worker_iface->end_run (plugin_instance); } /* wake up UI */ if (gui_instance && ( jack_ringbuffer_read_space (rb_ctrl_to_ui) >= sizeof (uint32_t) + sizeof (float) || jack_ringbuffer_read_space (rb_atom_to_ui) > sizeof (LV2_Atom) #ifdef HAVE_LIBLO || jack_ringbuffer_read_space (rb_osc_to_ui) >= sizeof (uint32_t) + sizeof (float) #endif ) ) { if (pthread_mutex_trylock (&gui_thread_lock) == 0) { pthread_cond_signal (&data_ready); pthread_mutex_unlock (&gui_thread_lock); } } return 0; } static void jack_shutdown (void* arg) { fprintf (stderr, "recv. shutdown request from jackd.\n"); client_state = Exit; pthread_cond_signal (&data_ready); } static int jack_graph_order_cb (void* arg) { worst_capture_latency = 0; for (uint32_t i = 0; i < inst->nports_audio_in; i++) { jack_port_get_latency_range (input_port[i], JackCaptureLatency, &(delayline[i]->port_latency)); if (delayline[i]->port_latency.max > worst_capture_latency) { worst_capture_latency = delayline[i]->port_latency.max; } } return 0; } static void jack_latency_cb (jack_latency_callback_mode_t mode, void* arg) { // assume 1 -> 1 map jack_graph_order_cb (NULL); // update worst-case latency, delayline alignment if (mode == JackCaptureLatency) { for (uint32_t i = 0; i < inst->nports_audio_out; i++) { jack_latency_range_t r; if (i < inst->nports_audio_in) { const uint32_t port_delay = worst_capture_latency - delayline[i]->port_latency.max; jack_port_get_latency_range (input_port[i], JackCaptureLatency, &r); r.min += port_delay; r.max += port_delay; } else { r.min = r.max = 0; } r.min += plugin_latency; r.max += plugin_latency; jack_port_set_latency_range (output_port[i], JackCaptureLatency, &r); } } else { // JackPlaybackLatency for (uint32_t i = 0; i < inst->nports_audio_in; i++) { const uint32_t port_delay = worst_capture_latency - delayline[i]->port_latency.max; jack_latency_range_t r; if (i < inst->nports_audio_out) { jack_port_get_latency_range (output_port[i], JackPlaybackLatency, &r); } else { r.min = r.max = 0; } r.min += port_delay + plugin_latency; r.max += port_delay + plugin_latency; jack_port_set_latency_range (input_port[i], JackPlaybackLatency, &r); } } } static void jack_freewheel_cb (int onoff, void* arg) { _freewheeling = onoff; } static int init_jack (const char* client_name) { jack_status_t status; char* cn = strdup (client_name); if (strlen (cn) >= (unsigned int)jack_client_name_size () - 1) { cn[jack_client_name_size () - 1] = '\0'; } j_client = jack_client_open (cn, JackNoStartServer, &status); free (cn); if (j_client == NULL) { fprintf (stderr, "jack_client_open() failed, status = 0x%2.0x\n", status); if (status & JackServerFailed) { fprintf (stderr, "Unable to connect to JACK server\n"); } return (-1); } if (status & JackServerStarted) { fprintf (stderr, "JACK server started\n"); } if (status & JackNameNotUnique) { client_name = jack_get_client_name (j_client); fprintf (stderr, "jack-client name: `%s'\n", client_name); } jack_set_process_callback (j_client, process, 0); jack_set_graph_order_callback (j_client, jack_graph_order_cb, 0); jack_set_latency_callback (j_client, jack_latency_cb, 0); jack_set_freewheel_callback (j_client, jack_freewheel_cb, 0); #ifndef WIN32 jack_on_shutdown (j_client, jack_shutdown, NULL); #endif j_samplerate = jack_get_sample_rate (j_client); return (0); } static int jack_portsetup (void) { /* Allocate data structures that depend on the number of ports. */ if (inst->nports_audio_in > 0) { input_port = (jack_port_t**)malloc (sizeof (jack_port_t*) * inst->nports_audio_in); delayline = (struct DelayBuffer**)calloc (inst->nports_audio_in, sizeof (struct DelayBuffer*)); } for (uint32_t i = 0; i < inst->nports_audio_in; i++) { if ((input_port[i] = jack_port_register (j_client, inst->ports[portmap_a_in[i]].name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput, 0)) == 0) { fprintf (stderr, "cannot register input port \"%s\"!\n", inst->ports[portmap_a_in[i]].name); return (-1); } delayline[i] = (struct DelayBuffer*)calloc (1, sizeof (struct DelayBuffer)); } if (inst->nports_audio_out > 0) { output_port = (jack_port_t**)malloc (sizeof (jack_port_t*) * inst->nports_audio_out); } for (uint32_t i = 0; i < inst->nports_audio_out; i++) { if ((output_port[i] = jack_port_register (j_client, inst->ports[portmap_a_out[i]].name, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput, 0)) == 0) { fprintf (stderr, "cannot register output port \"%s\"!\n", inst->ports[portmap_a_out[i]].name); return (-1); } } if (inst->nports_midi_in) { if ((midi_in = jack_port_register (j_client, inst->ports[portmap_atom_from_ui].name, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput, 0)) == 0) { fprintf (stderr, "cannot register midi input port \"%s\"!\n", inst->ports[portmap_atom_from_ui].name); return (-1); } } if (inst->nports_midi_out) { if ((midi_out = jack_port_register (j_client, inst->ports[portmap_atom_to_ui].name, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput, 0)) == 0) { fprintf (stderr, "cannot register midi output port \"%s\"!\n", inst->ports[portmap_atom_to_ui].name); return (-1); } } jack_graph_order_cb (NULL); // query port latencies jack_recompute_total_latencies (j_client); return (0); } static void jack_portconnect (int which) { if (which & 1) { // connect audio input(s) const char** ports = jack_get_ports (j_client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsOutput | JackPortIsPhysical); for (uint32_t i = 0; i < inst->nports_audio_in && ports && ports[i]; i++) { if (jack_connect (j_client, ports[i], jack_port_name (input_port[i]))) break; } if (ports) { jack_free (ports); } } if (which & 2) { // connect audio outputs(s) const char** ports = jack_get_ports (j_client, NULL, JACK_DEFAULT_AUDIO_TYPE, JackPortIsInput | JackPortIsPhysical); for (uint32_t i = 0; i < inst->nports_audio_out && ports && ports[i]; i++) { if (jack_connect (j_client, jack_port_name (output_port[i]), ports[i])) break; } if (ports) { jack_free (ports); } } if ((which & 4) && midi_in) { // midi in const char** ports = jack_get_ports (j_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsOutput | JackPortIsPhysical); if (ports && ports[0]) { jack_connect (j_client, ports[0], jack_port_name (midi_in)); } if (ports) { jack_free (ports); } } if ((which & 8) && midi_out) { // midi out const char** ports = jack_get_ports (j_client, NULL, JACK_DEFAULT_MIDI_TYPE, JackPortIsInput | JackPortIsPhysical); if (ports && ports[0]) { jack_connect (j_client, jack_port_name (midi_out), ports[0]); } if (ports) { jack_free (ports); } } } /****************************************************************************** * LV2 */ static uint32_t uri_to_id (LV2_URID_Map_Handle handle, const char* uri) { for (uint32_t i = 0; i < urimap_len; ++i) { if (!strcmp (urimap[i], uri)) { //printf("Found mapped URI '%s' -> %d\n", uri, i + 1); return i + 1; } } //printf("map URI '%s' -> %d\n", uri, urimap_len + 1); urimap = (char**)realloc (urimap, (urimap_len + 1) * sizeof (char*)); urimap[urimap_len] = strdup (uri); return ++urimap_len; } static void free_uri_map () { for (uint32_t i = 0; i < urimap_len; ++i) { free (urimap[i]); } free (urimap); } static void write_function ( LV2UI_Controller controller, uint32_t port_index, uint32_t buffer_size, uint32_t port_protocol, const void* buffer) { if (buffer_size == 0) return; if (port_protocol != 0) { if (jack_ringbuffer_write_space (rb_atom_from_ui) >= buffer_size + sizeof (LV2_Atom)) { LV2_Atom a = { buffer_size, 0 }; jack_ringbuffer_write (rb_atom_from_ui, (char*)&a, sizeof (LV2_Atom)); jack_ringbuffer_write (rb_atom_from_ui, (char*)buffer, buffer_size); } return; } if (buffer_size != sizeof (float)) { fprintf (stderr, "LV2Host: write_function() unsupported buffer\n"); return; } if (port_index >= inst->nports_total) { fprintf (stderr, "LV2Host: write_function() invalid port\n"); return; } if (portmap_ctrl[port_index] == UINT32_MAX) { fprintf (stderr, "LV2Host: write_function() unmapped port\n"); return; } if (inst->ports[port_index].porttype != CONTROL_IN) { fprintf (stderr, "LV2Host: write_function() not a control input\n"); return; } if (jack_ringbuffer_write_space (rb_ctrl_from_ui) >= sizeof (uint32_t) + sizeof (float)) { pthread_mutex_lock (&port_write_lock); jack_ringbuffer_write (rb_ctrl_from_ui, (char*)&portmap_ctrl[port_index], sizeof (uint32_t)); jack_ringbuffer_write (rb_ctrl_from_ui, (char*)buffer, sizeof (float)); pthread_mutex_unlock (&port_write_lock); } } // LV2 Worker static LV2_Worker_Status lv2_worker_respond (LV2_Worker_Respond_Handle unused, uint32_t size, const void* data) { jack_ringbuffer_write (worker_responses, (const char*)&size, sizeof (size)); jack_ringbuffer_write (worker_responses, (const char*)data, size); return LV2_WORKER_SUCCESS; } static void* worker_func (void* data) { pthread_mutex_lock (&worker_lock); while (1) { char buf[4096]; uint32_t size = 0; if (jack_ringbuffer_read_space (worker_requests) <= sizeof (size)) { pthread_cond_wait (&worker_ready, &worker_lock); } if (client_state == Exit) break; jack_ringbuffer_read (worker_requests, (char*)&size, sizeof (size)); if (size > 4096) { fprintf (stderr, "Worker information is too large. Abort.\n"); break; } jack_ringbuffer_read (worker_requests, buf, size); worker_iface->work (plugin_instance, lv2_worker_respond, NULL, size, buf); } pthread_mutex_unlock (&worker_lock); return NULL; } static void worker_init () { worker_requests = jack_ringbuffer_create (4096); worker_responses = jack_ringbuffer_create (4096); jack_ringbuffer_mlock (worker_requests); jack_ringbuffer_mlock (worker_responses); pthread_create (&worker_thread, NULL, worker_func, NULL); } static LV2_Worker_Status lv2_worker_schedule (LV2_Worker_Schedule_Handle unused, uint32_t size, const void* data) { if (_freewheeling) { worker_iface->work (plugin_instance, lv2_worker_respond, NULL, size, data); return LV2_WORKER_SUCCESS; } assert (worker_requests); jack_ringbuffer_write (worker_requests, (const char*)&size, sizeof (size)); jack_ringbuffer_write (worker_requests, (const char*)data, size); if (pthread_mutex_trylock (&worker_lock) == 0) { pthread_cond_signal (&worker_ready); pthread_mutex_unlock (&worker_lock); } return LV2_WORKER_SUCCESS; } /****************************************************************************** * OSC */ #ifdef HAVE_LIBLO static void osc_queue_midi_event (osc_midi_event_t* ev) { if (((queued_events_start + 1) % OSC_MIDI_QUEUE_SIZE) == queued_events_end) { return; } memcpy (&event_queue[queued_events_start], ev, sizeof (osc_midi_event_t)); queued_events_start = (queued_events_start + 1) % OSC_MIDI_QUEUE_SIZE; } static void oscb_error (int num, const char* m, const char* path) { fprintf (stderr, "liblo server error %d in path %s: %s\n", num, path, m); } #define MIDI_Q3(STATUS) \ osc_midi_event_t ev; \ ev.size = 3; \ ev.buffer[0] = STATUS | (argv[0]->i & 0x0f); \ ev.buffer[1] = argv[1]->i & 0x7f; \ ev.buffer[2] = argv[2]->i & 0x7f; \ osc_queue_midi_event (&ev); static int oscb_noteon (const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) { MIDI_Q3 (0x90); return 0; } static int oscb_noteoff (const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) { MIDI_Q3 (0x80); return 0; } static int oscb_cc (const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) { MIDI_Q3 (0xb0); return 0; } static int oscb_pc (const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) { osc_midi_event_t ev; ev.size = 2; ev.buffer[0] = 0xc0 | (argv[0]->i & 0x0f); ev.buffer[1] = argv[1]->i & 0x7f; ev.buffer[2] = 0x00; osc_queue_midi_event (&ev); return 0; } static int oscb_rawmidi (const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) { osc_midi_event_t ev; ev.size = 3; ev.buffer[0] = argv[0]->m[1]; ev.buffer[1] = argv[0]->m[2] & 0x7f; ev.buffer[2] = argv[0]->m[3] & 0x7f; osc_queue_midi_event (&ev); return 0; } static int oscb_parameter (const char* path, const char* types, lo_arg** argv, int argc, lo_message msg, void* user_data) { assert (argc == 2 && !strcmp (types, "if")); const uint32_t port = argv[0]->i; const float val = argv[1]->f; if (inst->nports_ctrl <= port) { fprintf (stderr, "OSC: Invalid Parameter 0 <= %d < %d\n", port, inst->nports_ctrl); return 0; } const uint32_t port_index = portmap_rctl[port]; if (inst->ports[port_index].porttype != CONTROL_IN) { fprintf (stderr, "OSC: mapped port (%d) is not a control input.\n", port_index); return 0; } if (inst->ports[port_index].val_min < inst->ports[port_index].val_max) { if (val < inst->ports[port_index].val_min || val > inst->ports[port_index].val_max) { fprintf (stderr, "OSC: Value out of bounds %f <= %f <= %f\n", inst->ports[port_index].val_min, val, inst->ports[port_index].val_max); return 0; } } //fprintf (stdout, "OSC: %d, %d -> %f\n", port, port_index, val); write_function (NULL, port_index, sizeof (float), 0, (const void*)&val); if (jack_ringbuffer_write_space (rb_osc_to_ui) >= sizeof (uint32_t) + sizeof (float)) { jack_ringbuffer_write (rb_osc_to_ui, (char*)&port_index, sizeof (uint32_t)); jack_ringbuffer_write (rb_osc_to_ui, (char*)&val, sizeof (float)); } return 0; } struct osc_command { const char* path; const char* typespec; lo_method_handler handler; const char* documentation; }; static struct osc_command OSCC[] = { { "/x42/parameter", "if", &oscb_parameter, "Set Control Input value. control-port, value" }, { "/x42/midi/raw", "m", &oscb_rawmidi, "Send raw midi message to plugin" }, { "/x42/midi/cc", "iii", &oscb_cc, "Send midi control-change: channel, parameter, value" }, { "/x42/midi/pc", "ii", &oscb_pc, "Send midi program-change: channel, program" }, { "/x42/midi/noteon", "iii", &oscb_noteon, "Send midi note on: channel, key, velocity" }, { "/x42/midi/noteoff", "iii", &oscb_noteoff, "Send midi note off: channel, key, velocity" }, }; static void print_oscdoc (void) { printf ("# X42 OSC methods. Format:\n"); printf ("# Path Typespec Documentation \n"); printf ("#######################################################\n"); for (size_t i = 0; i < sizeof (OSCC) / sizeof (struct osc_command); ++i) { printf ("%s %s %s\n", OSCC[i].path, OSCC[i].typespec, OSCC[i].documentation); } } static int start_osc_server (int osc_port) { char tmp[8]; uint32_t port = (osc_port > 100 && osc_port < 60000) ? osc_port : 9988; #ifdef _WIN32 sprintf (tmp, "%d", port); #else snprintf (tmp, sizeof (tmp), "%d", port); #endif osc_server = lo_server_thread_new (tmp, oscb_error); if (!osc_server) { fprintf (stderr, "OSC failed to listen on port %s", tmp); return -1; } else { char* urlstr = lo_server_thread_get_url (osc_server); fprintf (stderr, "OSC server: %s\n", urlstr); free (urlstr); } for (size_t i = 0; i < sizeof (OSCC) / sizeof (struct osc_command); ++i) { lo_server_thread_add_method (osc_server, OSCC[i].path, OSCC[i].typespec, OSCC[i].handler, NULL); } lo_server_thread_start (osc_server); return 0; } static void stop_osc_server () { if (!osc_server) return; lo_server_thread_stop (osc_server); lo_server_thread_free (osc_server); fprintf (stderr, "OSC server shut down.\n"); osc_server = NULL; } #endif /****************************************************************************** * MAIN */ static void cleanup (int sig) { if (j_client) { jack_client_close (j_client); j_client = NULL; } if (worker_requests) { pthread_mutex_lock (&worker_lock); pthread_cond_signal (&worker_ready); pthread_mutex_unlock (&worker_lock); pthread_join (worker_thread, NULL); jack_ringbuffer_free (worker_requests); jack_ringbuffer_free (worker_responses); } #ifdef HAVE_LIBLO stop_osc_server (); #endif if (plugin_dsp && plugin_instance && plugin_dsp->deactivate) { plugin_dsp->deactivate (plugin_instance); } if (plugin_gui && gui_instance && plugin_gui->cleanup) { plugin_gui->cleanup (gui_instance); } if (plugin_dsp && plugin_instance && plugin_dsp->cleanup) { plugin_dsp->cleanup (plugin_instance); } jack_ringbuffer_free (rb_ctrl_to_ui); jack_ringbuffer_free (rb_ctrl_from_ui); jack_ringbuffer_free (rb_atom_to_ui); jack_ringbuffer_free (rb_atom_from_ui); #ifdef HAVE_LIBLO jack_ringbuffer_free (rb_osc_to_ui); #endif free (input_port); free (output_port); if (delayline) { for (uint32_t i = 0; i < inst->nports_audio_in; i++) { free (delayline[i]); } } free (delayline); free (plugin_ports_pre); free (plugin_ports_post); free (portmap_a_in); free (portmap_a_out); free (portmap_ctrl); free (portmap_rctl); free (atom_in); free (atom_out); free_uri_map (); fprintf (stderr, "bye.\n"); } static void run_one (LV2_Atom_Sequence* data) { #ifdef HAVE_LIBLO while (jack_ringbuffer_read_space (rb_osc_to_ui) >= sizeof (uint32_t) + sizeof (float)) { uint32_t idx; float val; jack_ringbuffer_read (rb_osc_to_ui, (char*)&idx, sizeof (uint32_t)); jack_ringbuffer_read (rb_osc_to_ui, (char*)&val, sizeof (float)); plugin_gui->port_event (gui_instance, idx, sizeof (float), 0, &val); } #endif while (jack_ringbuffer_read_space (rb_ctrl_to_ui) >= sizeof (uint32_t) + sizeof (float)) { uint32_t idx; float val; jack_ringbuffer_read (rb_ctrl_to_ui, (char*)&idx, sizeof (uint32_t)); jack_ringbuffer_read (rb_ctrl_to_ui, (char*)&val, sizeof (float)); plugin_gui->port_event (gui_instance, idx, sizeof (float), 0, &val); if (idx == inst->latency_ctrl_port) { // jack client calls cannot be done in the DSP thread with jack1 jack_recompute_total_latencies (j_client); } } while (jack_ringbuffer_read_space (rb_atom_to_ui) > sizeof (LV2_Atom)) { LV2_Atom a; jack_ringbuffer_read (rb_atom_to_ui, (char*)&a, sizeof (LV2_Atom)); assert (a.size < inst->min_atom_bufsiz); jack_ringbuffer_read (rb_atom_to_ui, (char*)data, a.size); LV2_Atom_Event const* ev = (LV2_Atom_Event const*)((&(data)->body) + 1); // lv2_atom_sequence_begin while ((const uint8_t*)ev < ((const uint8_t*)&(data)->body + (data)->atom.size)) { plugin_gui->port_event (gui_instance, portmap_atom_to_ui, ev->body.size, uri_atom_EventTransfer, &ev->body); ev = (LV2_Atom_Event const*)/* lv2_atom_sequence_next() */ ((const uint8_t*)ev + sizeof (LV2_Atom_Event) + ((ev->body.size + 7) & ~7)); } } LV2_EXTERNAL_UI_RUN (extui); } #ifdef __APPLE__ static void osx_loop (CFRunLoopTimerRef timer, void* info) { if (client_state == Run) { run_one ((LV2_Atom_Sequence*)info); } if (client_state == Exit) { rtk_osx_api_terminate (); } } #else static void main_loop (void) { struct timespec timeout; LV2_Atom_Sequence* data = (LV2_Atom_Sequence*)malloc (inst->min_atom_bufsiz * sizeof (uint8_t)); pthread_mutex_lock (&gui_thread_lock); while (client_state != Exit) { run_one (data); if (client_state == Exit) break; #ifdef _WIN32 //Sleep(1000/UI_UPDATE_FPS); #if (defined(__MINGW64__) || defined(__MINGW32__)) && __MSVCRT_VERSION__ >= 0x0601 struct __timeb64 timebuffer; _ftime64 (&timebuffer); #else struct __timeb32 timebuffer; _ftime (&timebuffer); #endif timeout.tv_nsec = timebuffer.millitm * 1000000; timeout.tv_sec = timebuffer.time; #else // POSIX clock_gettime (CLOCK_REALTIME, &timeout); #endif timeout.tv_nsec += 1000000000 / (UI_UPDATE_FPS); if (timeout.tv_nsec >= 1000000000) { timeout.tv_nsec -= 1000000000; timeout.tv_sec += 1; } pthread_cond_timedwait (&data_ready, &gui_thread_lock, &timeout); } /* while running */ free (data); pthread_mutex_unlock (&gui_thread_lock); } #endif // APPLE RUNLOOP static void catchsig (int sig) { fprintf (stderr, "caught signal - shutting down.\n"); client_state = Exit; pthread_cond_signal (&data_ready); } static void on_external_ui_closed (void* controller) { catchsig (0); } #ifdef X42_MULTIPLUGIN static void list_plugins (void) { unsigned int i; for (i = 0; i < sizeof (_plugins) / sizeof (RtkLv2Description); ++i) { const LV2_Descriptor* d = _plugins[i].lv2_descriptor (_plugins[i].dsp_descriptor_id); printf (" %2d \"%s\" %s\n", i, _plugins[i].plugin_human_id, d->URI); } } #endif static void print_usage (void) { #ifdef X42_MULTIPLUGIN printf ("x42-%s - JACK %s\n\n", APPNAME, X42_MULTIPLUGIN_NAME); printf ("Usage: x42-%s [ OPTIONS ] [ Plugin-ID or URI ]\n\n", APPNAME); printf ("This is a standalone JACK application of a collection of LV2 plugins.\n" "Use ID -1, -l or --list for a dedicated list of included plugins.\n" "By default the first listed plugin (ID 0) is used.\n\n"); printf ("List of available plugins: (ID \"Name\" URI)\n"); list_plugins (); #else #if defined X42_PLUGIN_STRUCT inst = &X42_PLUGIN_STRUCT; #else inst = &_plugin; #endif const LV2_Descriptor* d = inst->lv2_descriptor (inst->dsp_descriptor_id); printf ("x42-%s - JACK %s\n\n", APPNAME, inst->plugin_human_id); printf ("Usage: x42-%s [ OPTIONS ]\n\n", APPNAME); printf ("This is a standalone JACK application of the LV2 plugin:\n" "\"%s\".\n", inst->plugin_human_id); #endif printf ("\nUsage:\n" "All control elements are operated in using the mouse:\n" " Click+Drag left/down: decrease, right/up: increase value. Hold the Ctrl key to increase sensitivity.\n" " Shift+Click reset to default value\n" " Scroll-wheel up/down by 1 step (smallest possible adjustment for given setting). Rapid continuous scrolling increases the step-size.\n" "The application can be closed by sending a SIGTERM (CTRL+C) on the command-line of by closing the window.\n" ); printf ("\nOptions:\n" " -h, --help Display this help and exit\n" " -j, --jack-name Set the JACK client name\n" " (defaults to plugin-name)\n" #ifndef REQUIRE_UI " -G, --nogui run headless, useful for OSC remote ctrl.\n" #endif #ifdef X42_MULTIPLUGIN " -l, --list Print list of available plugins and exit\n" #endif " -O , --osc Listen for OSC messages on the given UDP port\n" " -p :, --port :\n" " Set initial value for given control port\n" " -P, --portlist Print control port list on startup\n" " --osc-doc Print available OSC commands and exit\n" " -V, --version Print version information and exit\n" ); #ifdef X42_MULTIPLUGIN_URI printf ("\nSee also: <%s>\n", X42_MULTIPLUGIN_URI); #else printf ("\nSee also: <%s>\n", d->URI); #endif printf ("Website: \n"); } static void print_version (void) { printf ("x42-%s version %s\n\n", APPNAME, VERSION); printf ("\n" "Copyright (C) GPL 2013-2019 Robin Gareus \n" "This is free software; see the source for copying conditions. There is NO\n" "warranty; not even for MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.\n\n"); } static void dump_control_ports (void) { printf ("# Input Control Port List (%d ports)\n", inst->nports_ctrl); for (uint32_t i = 0; i < inst->nports_ctrl; ++i) { const uint32_t pi = portmap_rctl[i]; if (inst->ports[pi].porttype != CONTROL_IN) { continue; } printf ("%3d: %s", i, inst->ports[pi].name); if (inst->ports[pi].val_min < inst->ports[pi].val_max) { printf (" (min: %.2f, max: %.2f)", inst->ports[pi].val_min, inst->ports[pi].val_max); } if (inst->ports[pi].val_default != nan) { printf (" [default: %.2f]", inst->ports[pi].val_default); } if (inst->ports[pi].doc) { printf (" -- %s", inst->ports[pi].doc); } printf ("\n"); } printf ("### End Port List\n"); } int main (int argc, char** argv) { int c; int rv = 0; int osc_port = 0; bool dump_ports = false; bool headless = false; uint32_t c_ain = 0; uint32_t c_aout = 0; uint32_t c_ctrl = 0; uint32_t n_pval = 0; struct PValue* pval = NULL; char* jack_client_name = NULL; const struct option long_options[] = { { "help", no_argument, 0, 'h' }, { "jack-name", required_argument, 0, 'j' }, { "list", no_argument, 0, 'l' }, { "nogui", no_argument, 0, 'G' }, { "osc", required_argument, 0, 'O' }, { "osc-doc", no_argument, 0, 0x100 }, { "port", required_argument, 0, 'p' }, { "portlist", no_argument, 0, 'P' }, { "version", no_argument, 0, 'V' }, }; const char* optstring = "Ghj:lO:p:PV1"; if (optind < argc && !strncmp (argv[optind], "-psn_0", 6)) { ++optind; } while ((c = getopt_long (argc, argv, optstring, long_options, NULL)) != -1) { char* tmp; switch (c) { case 'h': print_usage (); return 0; break; case 'G': #ifndef REQUIRE_UI headless = true; #endif break; case 'j': jack_client_name = optarg; break; case '1': // catch -1, backward compat case 'l': #ifdef X42_MULTIPLUGIN list_plugins (); #endif return 0; break; case 'O': osc_port = atoi (optarg); #ifndef HAVE_LIBLO fprintf (stderr, "This version was compiled without OSC support.\n"); #endif break; case 'p': if ((tmp = strchr (optarg, ':')) != NULL && *(++tmp)) { pval = (struct PValue*)realloc (pval, (n_pval + 1) * sizeof (struct PValue)); pval[n_pval].port_idx = atoi (optarg); pval[n_pval].value = atof (tmp); ++n_pval; } break; case 'P': dump_ports = true; break; case 'V': print_version (); return 0; break; case 0x100: #ifndef HAVE_LIBLO fprintf (stderr, "This version was compiled without OSC support.\n"); #else print_oscdoc (); #endif return 0; break; default: // silently ignore additional session options on OSX #ifndef __APPLE__ fprintf (stderr, "Invalid argument. See --help for usage information.\n"); return (1); #endif break; } } // TODO autoconnect option. #ifdef X42_MULTIPLUGIN inst = NULL; if (optind < argc && atoi (argv[optind]) < 0) { list_plugins (); return 0; } if (optind < argc && strlen (argv[optind]) > 2 && atoi (argv[optind]) == 0) { unsigned int i; for (i = 0; i < sizeof (_plugins) / sizeof (RtkLv2Description); ++i) { const LV2_Descriptor* d = _plugins[i].lv2_descriptor (_plugins[i].dsp_descriptor_id); if (strstr (d->URI, argv[optind]) || strstr (_plugins[i].plugin_human_id, argv[optind])) { inst = &_plugins[i]; break; } } } if (optind < argc && !inst && atoi (argv[optind]) >= 0) { unsigned int plugid = atoi (argv[optind]); if (plugid < (sizeof (_plugins) / sizeof (RtkLv2Description))) { inst = &_plugins[plugid]; } } if (!inst) { inst = &_plugins[0]; } #elif defined X42_PLUGIN_STRUCT inst = &X42_PLUGIN_STRUCT; #else inst = &_plugin; #endif #ifdef __APPLE__ rtk_osx_api_init (); #endif #ifdef USE_WEAK_JACK if (have_libjack ()) { fprintf (stderr, "JACK is not available. http://jackaudio.org/\n"); #ifdef _WIN32 MessageBox (NULL, TEXT ( "JACK is not available.\n" "You must have the JACK Audio Connection Kit installed to use the tools. " "Please see http://jackaudio.org/ and http://jackaudio.org/faq/jack_on_windows.html"), TEXT ("Error"), MB_ICONERROR | MB_OK); #elif __APPLE__ rtk_osx_api_err ( "JACK is not available.\n" "You must have the JACK Audio Connection Kit installed to use the tools. " "Please see http://jackaudio.org/"); #endif return 1; } #endif #ifdef _WIN32 pthread_win32_process_attach_np (); #endif #if (defined _WIN32 && defined RTK_STATIC_INIT) glib_init_static (); gobject_init_ctor (); #endif LV2_URID_Map uri_map = { NULL, &uri_to_id }; const LV2_Feature map_feature = { LV2_URID__map, &uri_map }; const LV2_Feature unmap_feature = { LV2_URID__unmap, NULL }; LV2_Worker_Schedule schedule = { NULL, lv2_worker_schedule }; const LV2_Feature schedule_feature = { LV2_WORKER__schedule, &schedule }; const LV2_Feature* features[] = { &map_feature, &unmap_feature, &schedule_feature, NULL }; const LV2_Feature external_lv_feature = { LV2_EXTERNAL_UI_URI, &extui_host }; const LV2_Feature external_kx_feature = { LV2_EXTERNAL_UI_URI__KX__Host, &extui_host }; LV2_Feature instance_feature = { "http://lv2plug.in/ns/ext/instance-access", NULL }; const LV2_Feature* ui_features[] = { &map_feature, &unmap_feature, &instance_feature, &external_lv_feature, &external_kx_feature, NULL }; /* check sourced settings */ assert ((inst->nports_midi_in + inst->nports_atom_in) <= 1); assert ((inst->nports_midi_out + inst->nports_atom_out) <= 1); assert (inst->plugin_human_id); assert (inst->nports_total > 0); extui_host.plugin_human_id = inst->plugin_human_id; // TODO check if allocs succeeded - OOM -> exit /* allocate data structure */ portmap_a_in = (uint32_t*)malloc (inst->nports_audio_in * sizeof (uint32_t)); portmap_a_out = (uint32_t*)malloc (inst->nports_audio_out * sizeof (uint32_t)); portmap_rctl = (uint32_t*)malloc (inst->nports_ctrl * sizeof (uint32_t)); portmap_ctrl = (uint32_t*)malloc (inst->nports_total * sizeof (uint32_t)); plugin_ports_pre = (float*)calloc (inst->nports_ctrl, sizeof (float)); plugin_ports_post = (float*)calloc (inst->nports_ctrl, sizeof (float)); atom_in = (LV2_Atom_Sequence*)malloc (inst->min_atom_bufsiz + sizeof (uint8_t)); atom_out = (LV2_Atom_Sequence*)malloc (inst->min_atom_bufsiz + sizeof (uint8_t)); rb_ctrl_to_ui = jack_ringbuffer_create ((UPDATE_FREQ_RATIO)*inst->nports_ctrl * 2 * sizeof (float)); rb_ctrl_from_ui = jack_ringbuffer_create ((UPDATE_FREQ_RATIO)*inst->nports_ctrl * 2 * sizeof (float)); rb_atom_to_ui = jack_ringbuffer_create ((UPDATE_FREQ_RATIO)*inst->min_atom_bufsiz); rb_atom_from_ui = jack_ringbuffer_create ((UPDATE_FREQ_RATIO)*inst->min_atom_bufsiz); #ifdef HAVE_LIBLO rb_osc_to_ui = jack_ringbuffer_create ((UPDATE_FREQ_RATIO)*inst->nports_ctrl * 2 * sizeof (float)); #endif /* resolve descriptors */ plugin_dsp = inst->lv2_descriptor (inst->dsp_descriptor_id); plugin_gui = inst->lv2ui_descriptor (inst->gui_descriptor_id); if (!plugin_dsp) { fprintf (stderr, "cannot resolve LV2 descriptor\n"); rv |= 2; goto out; } /* jack-open -> samlerate */ if (init_jack (jack_client_name ? jack_client_name : extui_host.plugin_human_id)) { fprintf (stderr, "cannot connect to JACK.\n"); #ifdef _WIN32 MessageBox (NULL, TEXT ( "Cannot connect to JACK.\n" "Please start the JACK Server first."), TEXT ("Error"), MB_ICONERROR | MB_OK); #elif __APPLE__ rtk_osx_api_err ( "Cannot connect to JACK.\n" "Please start the JACK Server first."); #else (void)system ("xmessage -button ok -center \"Cannot connect to JACK.\nPlease start the JACK Server first.\" &"); #endif rv |= 4; goto out; } /* init plugin */ plugin_instance = plugin_dsp->instantiate (plugin_dsp, j_samplerate, NULL, features); if (!plugin_instance) { fprintf (stderr, "instantiation failed\n"); rv |= 2; goto out; } /* connect ports */ for (uint32_t p = 0; p < inst->nports_total; ++p) { portmap_ctrl[p] = UINT32_MAX; switch (inst->ports[p].porttype) { case CONTROL_IN: plugin_ports_pre[c_ctrl] = inst->ports[p].val_default; case CONTROL_OUT: portmap_ctrl[p] = c_ctrl; portmap_rctl[c_ctrl] = p; plugin_dsp->connect_port (plugin_instance, p, &plugin_ports_pre[c_ctrl++]); break; case AUDIO_IN: portmap_a_in[c_ain++] = p; break; case AUDIO_OUT: portmap_a_out[c_aout++] = p; break; case MIDI_IN: case ATOM_IN: portmap_atom_from_ui = p; plugin_dsp->connect_port (plugin_instance, p, atom_in); break; case MIDI_OUT: case ATOM_OUT: portmap_atom_to_ui = p; plugin_dsp->connect_port (plugin_instance, p, atom_out); break; default: fprintf (stderr, "yet unsupported port..\n"); break; } } assert (c_ain == inst->nports_audio_in); assert (c_aout == inst->nports_audio_out); assert (c_ctrl == inst->nports_ctrl); if (inst->nports_atom_out > 0 || inst->nports_atom_in > 0 || inst->nports_midi_in > 0 || inst->nports_midi_out > 0) { uri_atom_Sequence = uri_to_id (NULL, LV2_ATOM__Sequence); uri_atom_EventTransfer = uri_to_id (NULL, LV2_ATOM__eventTransfer); uri_midi_MidiEvent = uri_to_id (NULL, LV2_MIDI__MidiEvent); uri_time_Position = uri_to_id (NULL, LV2_TIME__Position); uri_time_frame = uri_to_id (NULL, LV2_TIME__frame); uri_time_speed = uri_to_id (NULL, LV2_TIME__speed); uri_time_bar = uri_to_id (NULL, LV2_TIME__bar); uri_time_barBeat = uri_to_id (NULL, LV2_TIME__barBeat); uri_time_beatUnit = uri_to_id (NULL, LV2_TIME__beatUnit); uri_time_beatsPerBar = uri_to_id (NULL, LV2_TIME__beatsPerBar); uri_time_beatsPerMinute = uri_to_id (NULL, LV2_TIME__beatsPerMinute); lv2_atom_forge_init (&lv2_forge, &uri_map); } if (dump_ports) { dump_control_ports (); } // apply user settings for (uint32_t i = 0; i < n_pval; ++i) { if (pval[i].port_idx >= inst->nports_ctrl) { fprintf (stderr, "Invalid Parameter 0 <= %d < %d\n", pval[i].port_idx, inst->nports_ctrl); continue; } const uint32_t port_index = portmap_rctl[pval[i].port_idx]; if (inst->ports[port_index].porttype != CONTROL_IN) { fprintf (stderr, "mapped port (%d) for port %d is not a control input.\n", port_index, pval[i].port_idx); continue; } if (inst->ports[port_index].val_min < inst->ports[port_index].val_max) { if (pval[i].value < inst->ports[port_index].val_min || pval[i].value > inst->ports[port_index].val_max) { fprintf (stderr, "Value for port %d out of bounds %f <= %f <= %f\n", pval[i].port_idx, inst->ports[port_index].val_min, pval[i].value, inst->ports[port_index].val_max); continue; } } //fprintf (stdout, "Port: %d, %d -> %f\n", pval[i].port_idx, port_index, pval[i].value); plugin_ports_pre[pval[i].port_idx] = pval[i].value; } if (jack_portsetup ()) { rv |= 12; goto out; } if (plugin_gui && !headless) { /* init plugin GUI */ extui_host.ui_closed = on_external_ui_closed; instance_feature.data = plugin_instance; gui_instance = plugin_gui->instantiate (plugin_gui, plugin_dsp->URI, NULL, &write_function, controller, (void**)&extui, ui_features); } if (!gui_instance || !extui) { #ifdef REQUIRE_UI fprintf (stderr, "Error: GUI initialization failed.\n"); rv |= 2; goto out; #else if (!headless) { fprintf (stderr, "Warning: GUI initialization failed.\n"); } #endif } #ifndef _WIN32 if (mlockall (MCL_CURRENT | MCL_FUTURE)) { fprintf (stderr, "Warning: Can not lock memory.\n"); } #endif if (gui_instance) { for (uint32_t p = 0; p < inst->nports_ctrl; p++) { if (jack_ringbuffer_write_space (rb_ctrl_to_ui) >= sizeof (uint32_t) + sizeof (float)) { jack_ringbuffer_write (rb_ctrl_to_ui, (char*)&portmap_rctl[p], sizeof (uint32_t)); jack_ringbuffer_write (rb_ctrl_to_ui, (char*)&plugin_ports_pre[p], sizeof (float)); } } } if (plugin_dsp->extension_data) { worker_iface = (LV2_Worker_Interface*)plugin_dsp->extension_data (LV2_WORKER__interface); } if (worker_iface) { worker_init (); } if (plugin_dsp->activate) { plugin_dsp->activate (plugin_instance); } if (jack_activate (j_client)) { fprintf (stderr, "cannot activate client.\n"); rv |= 20; goto out; } jack_portconnect (JACK_AUTOCONNECT); #ifdef HAVE_LIBLO if (osc_port > 0) { start_osc_server (osc_port); } #endif #ifndef _WIN32 signal (SIGHUP, catchsig); signal (SIGINT, catchsig); #endif if (!gui_instance || !extui) { /* no GUI */ while (client_state != Exit) { sleep (1); } } else { LV2_EXTERNAL_UI_SHOW (extui); #ifdef __APPLE__ LV2_Atom_Sequence* data = (LV2_Atom_Sequence*)malloc (inst->min_atom_bufsiz * sizeof (uint8_t)); CFRunLoopRef runLoop = CFRunLoopGetCurrent (); CFRunLoopTimerContext context = { 0, data, NULL, NULL, NULL }; CFRunLoopTimerRef timer = CFRunLoopTimerCreate (kCFAllocatorDefault, 0, 1.0 / UI_UPDATE_FPS, 0, 0, &osx_loop, &context); CFRunLoopAddTimer (runLoop, timer, kCFRunLoopCommonModes); rtk_osx_api_run (); free (data); #else main_loop (); #endif LV2_EXTERNAL_UI_HIDE (extui); } out: free (pval); cleanup (0); #ifdef _WIN32 pthread_win32_process_detach_np (); #endif #if (defined _WIN32 && defined RTK_STATIC_INIT) glib_cleanup_static (); #endif return (rv); } robtk-0.8.5/jackwrap.mm000066400000000000000000000022431463170413500150070ustar00rootroot00000000000000#ifdef __APPLE__ #import static void makeAppMenu (void) { id menubar = [[NSMenu new] autorelease]; id appMenuItem = [[NSMenuItem new] autorelease]; [menubar addItem:appMenuItem]; [NSApp setMainMenu:menubar]; id appMenu = [[NSMenu new] autorelease]; [appMenu addItemWithTitle:@"Quit" action:@selector(terminate:) keyEquivalent:@"q"]; [appMenuItem setSubmenu:appMenu]; } void rtk_osx_api_init(void) { [NSAutoreleasePool new]; [NSApplication sharedApplication]; makeAppMenu (); //[NSApp setDelegate:[NSApplication sharedApplication]]; //[NSApp finishLaunching]; } #ifndef OSX_SHUTDOWN_WAIT #define OSX_SHUTDOWN_WAIT 100 // run loop callbacks, jack to allow graceful close #endif void rtk_osx_api_terminate(void) { static int term_from_term = 0; if (term_from_term) { [[NSApplication sharedApplication] stop:nil]; } if (++term_from_term > OSX_SHUTDOWN_WAIT) { [[NSApplication sharedApplication] terminate:nil]; } } void rtk_osx_api_run(void) { [NSApp run]; } void rtk_osx_api_err(const char *msg) { NSAlert *alert = [[[NSAlert alloc] init] autorelease]; [alert setMessageText:[NSString stringWithUTF8String:msg]]; [alert runModal]; } #endif robtk-0.8.5/lv2_rgext.h000066400000000000000000000112441463170413500147400ustar00rootroot00000000000000/* Copyright 2016 Robin Gareus Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ #ifndef LV2_RG_EXT_H // -> needs to eventually go upstream to lv2plug.in #define LV2_RG_EXT_H #ifdef HAVE_LV2_1_18_6 #include #else #include #endif /** @defgroup inlinedisplay Inline-Display Support for displaying a miniaturized generic view directly in the host's Mixer Window. @{ */ #define LV2_INLINEDISPLAY_URI "http://harrisonconsoles.com/lv2/inlinedisplay" #define LV2_INLINEDISPLAY_PREFIX LV2_INLINEDISPLAY_URI "#" #define LV2_INLINEDISPLAY__interface LV2_INLINEDISPLAY_PREFIX "interface" #define LV2_INLINEDISPLAY__queue_draw LV2_INLINEDISPLAY_PREFIX "queue_draw" /** Opaque handle for LV2_Inline_Display::queue_draw() */ typedef void* LV2_Inline_Display_Handle; /** raw image pixmap format is ARGB32, * the data pointer is owned by the plugin and must be valid * from the first call to render until cleanup. */ typedef struct { unsigned char *data; int width; int height; int stride; } LV2_Inline_Display_Image_Surface; /** a LV2 Feature provided by the Host to the plugin */ typedef struct { /** Opaque host data */ LV2_Inline_Display_Handle handle; /** Request from run() that the host should call render() at a later time * to update the inline display */ void (*queue_draw)(LV2_Inline_Display_Handle handle); } LV2_Inline_Display; /** * Plugin Inline-Display Interface. */ typedef struct { /** * The render method. This is called by the host in a non-realtime context, * usually the main GUI thread. * The data pointer is owned by the plugin and must be valid * from the first call to render until cleanup. * * @param instance The LV2 instance * @param w the max available width * @param h the max available height * @return pointer to a LV2_Inline_Display_Image_Surface or NULL */ LV2_Inline_Display_Image_Surface* (*render)(LV2_Handle instance, uint32_t w, uint32_t h); } LV2_Inline_Display_Interface; /** @} */ /** @defgroup automate Self-Automation Support for plugins to write automation data via Atom Events @{ */ #define LV2_AUTOMATE_URI "http://ardour.org/lv2/automate" #define LV2_AUTOMATE_URI_PREFIX LV2_AUTOMATE_URI "#" /** an lv2:optionalFeature */ #define LV2_AUTOMATE_URI__can_write LV2_AUTOMATE_URI_PREFIX "canWriteAutomatation" /** atom:supports */ #define LV2_AUTOMATE_URI__control LV2_AUTOMATE_URI_PREFIX "automationControl" /** lv2:portProperty */ #define LV2_AUTOMATE_URI__controlled LV2_AUTOMATE_URI_PREFIX "automationControlled" /** atom messages */ #define LV2_AUTOMATE_URI__event LV2_AUTOMATE_URI_PREFIX "event" #define LV2_AUTOMATE_URI__setup LV2_AUTOMATE_URI_PREFIX "setup" #define LV2_AUTOMATE_URI__finalize LV2_AUTOMATE_URI_PREFIX "finalize" #define LV2_AUTOMATE_URI__start LV2_AUTOMATE_URI_PREFIX "start" #define LV2_AUTOMATE_URI__end LV2_AUTOMATE_URI_PREFIX "end" #define LV2_AUTOMATE_URI__parameter LV2_AUTOMATE_URI_PREFIX "parameter" #define LV2_AUTOMATE_URI__value LV2_AUTOMATE_URI_PREFIX "value" /** @} */ /** @defgroup license License-Report Allow for commercial LV2 to report their licensing status. @{ */ #define LV2_PLUGINLICENSE_URI "http://harrisonconsoles.com/lv2/license" #define LV2_PLUGINLICENSE_PREFIX LV2_PLUGINLICENSE_URI "#" #define LV2_PLUGINLICENSE__interface LV2_PLUGINLICENSE_PREFIX "interface" typedef struct _LV2_License_Interface { /* @return -1 if no license is needed; 0 if unlicensed, 1 if licensed */ int (*is_licensed)(LV2_Handle instance); /* @return a string copy of the licensee name if licensed, or NULL, the caller needs to free this */ char* (*licensee)(LV2_Handle instance); /* @return a URI identifying the plugin-bundle or plugin for which a given license is valid */ const char* (*product_uri)(LV2_Handle instance); /* @return human readable product name for the URI */ const char* (*product_name)(LV2_Handle instance); /* @return link to website or webstore */ const char* (*store_url)(LV2_Handle instance); } LV2_License_Interface; /** @} */ #endif robtk-0.8.5/lv2syms000066400000000000000000000000201463170413500142030ustar00rootroot00000000000000_lv2_descriptor robtk-0.8.5/lv2uisyms000066400000000000000000000000221463170413500145430ustar00rootroot00000000000000_lv2ui_descriptor robtk-0.8.5/pugl/000077500000000000000000000000001463170413500136205ustar00rootroot00000000000000robtk-0.8.5/pugl/pugl.h000066400000000000000000000247061463170413500147510ustar00rootroot00000000000000/* Copyright 2012 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** @file pugl.h API for Pugl, a minimal portable API for OpenGL. */ #ifndef PUGL_H_INCLUDED #define PUGL_H_INCLUDED #include /* This API is pure portable C and contains no platform specific elements, or even a GL dependency. However, unfortunately GL includes vary across platforms so they are included here to allow for pure portable programs. */ #ifdef __APPLE__ # include "OpenGL/gl.h" #else # ifdef _WIN32 # include /* Broken Windows GL headers require this */ # endif # include "GL/gl.h" #endif #ifdef _WIN32 #define PUGL_API #else #define PUGL_API __attribute__((visibility("hidden"))) #endif #ifdef __cplusplus extern "C" { #else # include #endif /** @defgroup pugl Pugl A minimal portable API for OpenGL. @{ */ /** An OpenGL view. */ typedef struct PuglViewImpl PuglView; /** A native window handle. On X11, this is a Window. On OSX, this is an NSView*. On Windows, this is a HWND. */ typedef intptr_t PuglNativeWindow; /** Return status code. */ typedef enum { PUGL_SUCCESS = 0 } PuglStatus; /** Convenience symbols for ASCII control characters. */ typedef enum { PUGL_CHAR_BACKSPACE = 0x08, PUGL_CHAR_ESCAPE = 0x1B, PUGL_CHAR_DELETE = 0x7F } PuglChar; /** Special (non-Unicode) keyboard keys. */ typedef enum { PUGL_KEY_F1 = 1, PUGL_KEY_F2, PUGL_KEY_F3, PUGL_KEY_F4, PUGL_KEY_F5, PUGL_KEY_F6, PUGL_KEY_F7, PUGL_KEY_F8, PUGL_KEY_F9, PUGL_KEY_F10, PUGL_KEY_F11, PUGL_KEY_F12, PUGL_KEY_LEFT, PUGL_KEY_UP, PUGL_KEY_RIGHT, PUGL_KEY_DOWN, PUGL_KEY_PAGE_UP, PUGL_KEY_PAGE_DOWN, PUGL_KEY_HOME, PUGL_KEY_END, PUGL_KEY_INSERT, PUGL_KEY_SHIFT, PUGL_KEY_CTRL, PUGL_KEY_ALT, PUGL_KEY_SUPER, } PuglKey; /** Keyboard modifier flags. */ typedef enum { PUGL_MOD_SHIFT = 1, /**< Shift key */ PUGL_MOD_CTRL = 1 << 1, /**< Control key */ PUGL_MOD_ALT = 1 << 2, /**< Alt/Option key */ PUGL_MOD_SUPER = 1 << 3, /**< Mod4/Command/Windows key */ } PuglMod; /** Handle for opaque user data. */ typedef void* PuglHandle; /** A function called when the window is closed. */ typedef void (*PuglCloseFunc)(PuglView* view); /** A function called to draw the view contents with OpenGL. */ typedef void (*PuglDisplayFunc)(PuglView* view); /** A function called when a key is pressed or released. @param view The view the event occured in. @param press True if the key was pressed, false if released. @param key Unicode point of the key pressed. */ typedef void (*PuglKeyboardFunc)(PuglView* view, bool press, uint32_t key); /** A function called when the pointer moves. @param view The view the event occured in. @param x The window-relative x coordinate of the pointer. @param y The window-relative y coordinate of the pointer. */ typedef void (*PuglMotionFunc)(PuglView* view, int x, int y); /** A function called when a mouse button is pressed or released. @param view The view the event occured in. @param button The button number (1 = left, 2 = middle, 3 = right). @param press True if the key was pressed, false if released. @param x The window-relative x coordinate of the pointer. @param y The window-relative y coordinate of the pointer. */ typedef void (*PuglMouseFunc)( PuglView* view, int button, bool press, int x, int y); /** A function called when the view is resized. @param view The view being resized. @param width The new view width. @param height The new view height. */ typedef void (*PuglReshapeFunc)(PuglView* view, int width, int height); /** Called outside glx-context when the plugin schedules a resize via puglPostResize. @param view The view being resized. @param width view width to resize to (variable is initialized to current size) @param height view height to resize to (variable is initialized to current size) @param set_hints if non-zero set window-hints */ typedef void (*PuglResizeFunc)(PuglView* view, int *width, int *height, int *set_hints); /** A function called on scrolling (e.g. mouse wheel or track pad). The distances used here are in "lines", a single tick of a clicking mouse wheel. For example, @p dy = 1.0 scrolls 1 line up. Some systems and devices support finer resolution and/or higher values for fast scrolls, so programs should handle any value gracefully. @param view The view being scrolled. @param x The window-relative x coordinate of the pointer. @param y The window-relative y coordinate of the pointer. @param dx The scroll x distance. @param dx The scroll y distance. */ typedef void (*PuglScrollFunc)(PuglView* view, int x, int y, float dx, float dy); /** A function called when a special key is pressed or released. This callback allows the use of keys that do not have unicode points. Note that some non-printable keys @param view The view the event occured in. @param press True if the key was pressed, false if released. @param key The key pressed. */ typedef void (*PuglSpecialFunc)(PuglView* view, bool press, PuglKey key); /** A function called when a filename is selected via file-browser. @param view The view the event occured in. @param filename The selected file name or NULL if the dialog was canceled. */ typedef void (*PuglFileSelectedFunc)(PuglView* view, const char* filename); /** A function called when a the main window's focus changed @param view The view the event occured in. @param enter True when focus is recevied, false when focus is lost */ typedef void (*PuglFocusFunc)(PuglView* view, bool enter); /** Create a new GL window. @param parent Parent window, or 0 for top level. @param title Window title, or NULL. @param width Window width in pixels. @param height Window height in pixels. @param resizable Whether window should be user resizable. */ PUGL_API PuglView* puglCreate(PuglNativeWindow parent, const char* title, int min_width, int min_height, int width, int height, bool resizable, bool ontop, unsigned long transientId); /** Set the handle to be passed to all callbacks. This is generally a pointer to a struct which contains all necessary state. Everything needed in callbacks should be here, not in static variables. Note the lack of this facility makes GLUT unsuitable for plugins or non-trivial programs; this mistake is largely why Pugl exists. */ PUGL_API void puglSetHandle(PuglView* view, PuglHandle handle); /** Get the handle to be passed to all callbacks. */ PUGL_API PuglHandle puglGetHandle(PuglView* view); /** Return the timestamp (if any) of the currently-processing event. */ PUGL_API uint32_t puglGetEventTimestamp(PuglView* view); /** Return the hardware backing scale */ PUGL_API float puglGetHWSurfaceScale(PuglView* view); /** Get the currently active modifiers (PuglMod flags). This should only be called from an event handler. */ PUGL_API int puglGetModifiers(PuglView* view); /** Ignore synthetic repeated key events. */ PUGL_API void puglIgnoreKeyRepeat(PuglView* view, bool ignore); /** Set the function to call when the window is closed. */ PUGL_API void puglSetCloseFunc(PuglView* view, PuglCloseFunc closeFunc); /** Set the display function which should draw the UI using GL. */ PUGL_API void puglSetDisplayFunc(PuglView* view, PuglDisplayFunc displayFunc); /** Set the function to call on keyboard events. */ PUGL_API void puglSetKeyboardFunc(PuglView* view, PuglKeyboardFunc keyboardFunc); /** Set the function to call on mouse motion. */ PUGL_API void puglSetMotionFunc(PuglView* view, PuglMotionFunc motionFunc); /** Set the function to call on mouse button events. */ PUGL_API void puglSetMouseFunc(PuglView* view, PuglMouseFunc mouseFunc); /** Set the function to call on scroll events. */ PUGL_API void puglSetScrollFunc(PuglView* view, PuglScrollFunc scrollFunc); /** Set the function to call on special events. */ PUGL_API void puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc); /** Set the function to call when the window size changes. */ PUGL_API void puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc); /** Set the function to call on file-browser selections. */ PUGL_API void puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc); /** Set the function to call on when windows focus changes */ PUGL_API void puglSetFocusFunc(PuglView* view, PuglFocusFunc focusFunc); PUGL_API int puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect); /** Open a file dialog window. */ PUGL_API int puglOpenFileDialog(PuglView* view, const char *title); /** Return the native window handle. */ PUGL_API PuglNativeWindow puglGetNativeWindow(PuglView* view); /** Process all pending window events. This handles input events as well as rendering, so it should be called regularly and rapidly enough to keep the UI responsive. */ PUGL_API PuglStatus puglProcessEvents(PuglView* view); /** Request a redisplay on the next call to puglProcessEvents(). */ PUGL_API void puglPostRedisplay(PuglView* view); /** Destroy a GL window. */ PUGL_API void puglDestroy(PuglView* view); /** Set callback function to change window size. */ PUGL_API void puglSetResizeFunc(PuglView* view, PuglResizeFunc resizeFunc); /** Request a resize on the next call to puglProcessEvents(). */ PUGL_API void puglPostResize(PuglView* view); /** Show Window (external ui) */ PUGL_API void puglHideWindow(PuglView* view); /** Hide Window (external ui) */ PUGL_API void puglShowWindow(PuglView* view); /** @} */ #ifdef __cplusplus } /* extern "C" */ #endif #endif /* PUGL_H_INCLUDED */ robtk-0.8.5/pugl/pugl_internal.h000066400000000000000000000072741463170413500166460ustar00rootroot00000000000000/* Copyright 2012 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** @file pugl_internal.h Private platform-independent definitions. Note this file contains function definitions, so it must be compiled into the final binary exactly once. Each platform specific implementation file including it once should achieve this. */ #include "pugl.h" typedef struct PuglInternalsImpl PuglInternals; struct PuglViewImpl { PuglHandle handle; PuglCloseFunc closeFunc; PuglDisplayFunc displayFunc; PuglKeyboardFunc keyboardFunc; PuglMotionFunc motionFunc; PuglMouseFunc mouseFunc; PuglReshapeFunc reshapeFunc; PuglResizeFunc resizeFunc; PuglScrollFunc scrollFunc; PuglSpecialFunc specialFunc; PuglFocusFunc focusFunc; PuglFileSelectedFunc fileSelectedFunc; PuglInternals* impl; int width; int height; int min_width; int min_height; int mods; bool mouse_in_view; bool ignoreKeyRepeat; bool redisplay; bool user_resizable; bool set_window_hints; bool ontop; bool resize; float ui_scale; uint32_t event_timestamp_ms; }; void puglSetHandle(PuglView* view, PuglHandle handle) { view->handle = handle; } PuglHandle puglGetHandle(PuglView* view) { return view->handle; } uint32_t puglGetEventTimestamp(PuglView* view) { return view->event_timestamp_ms; } float puglGetHWSurfaceScale(PuglView* view) { return view->ui_scale;; } int puglGetModifiers(PuglView* view) { return view->mods; } static void puglDefaultReshape(PuglView* view, int width, int height) { glViewport(0, 0, width, height); glMatrixMode(GL_PROJECTION); glLoadIdentity(); glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); glClear (GL_COLOR_BUFFER_BIT); glMatrixMode(GL_MODELVIEW); glLoadIdentity(); } void puglIgnoreKeyRepeat(PuglView* view, bool ignore) { view->ignoreKeyRepeat = ignore; } void puglSetCloseFunc(PuglView* view, PuglCloseFunc closeFunc) { view->closeFunc = closeFunc; } void puglSetDisplayFunc(PuglView* view, PuglDisplayFunc displayFunc) { view->displayFunc = displayFunc; } void puglSetKeyboardFunc(PuglView* view, PuglKeyboardFunc keyboardFunc) { view->keyboardFunc = keyboardFunc; } void puglSetMotionFunc(PuglView* view, PuglMotionFunc motionFunc) { view->motionFunc = motionFunc; } void puglSetMouseFunc(PuglView* view, PuglMouseFunc mouseFunc) { view->mouseFunc = mouseFunc; } void puglSetReshapeFunc(PuglView* view, PuglReshapeFunc reshapeFunc) { view->reshapeFunc = reshapeFunc; } void puglSetResizeFunc(PuglView* view, PuglResizeFunc resizeFunc) { view->resizeFunc = resizeFunc; } void puglSetScrollFunc(PuglView* view, PuglScrollFunc scrollFunc) { view->scrollFunc = scrollFunc; } void puglSetSpecialFunc(PuglView* view, PuglSpecialFunc specialFunc) { view->specialFunc = specialFunc; } void puglSetFocusFunc(PuglView* view, PuglFocusFunc focusFunc) { view->focusFunc = focusFunc; } void puglSetFileSelectedFunc(PuglView* view, PuglFileSelectedFunc fileSelectedFunc) { view->fileSelectedFunc = fileSelectedFunc; } robtk-0.8.5/pugl/pugl_osx.mm000066400000000000000000000406571463170413500160270ustar00rootroot00000000000000/* Copyright 2012 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** @file pugl_osx.m OSX/Cocoa Pugl Implementation. */ #include #include #include #import #include "pugl_internal.h" // screw apple, their lack of -fvisibility=internal and namespaces #define CONCAT(A,B) A ## B #define XCONCAT(A,B) CONCAT(A,B) #define RobTKPuglWindow XCONCAT(RobTKPuglWindow, UINQHACK) #define RobTKPuglOpenGLView XCONCAT(RobTKPuglOpenGLView, UINQHACK) static int macOS_Version () { /* ideally we'd use (__builtin_available(macOS 10.7, *)), but that's n/a for old gcc builds */ static short int version[3] = {0, 0, 0}; if (0 == version[0]) { version[0] = -1; char buf[256]; size_t sz = sizeof (buf); if (0 == sysctlbyname ("kern.osrelease", buf, &sz, NULL, 0)) { sscanf (buf, "%hd.%hd.%hd", &version[0], &version[1], &version[2]); } } return version[0]; } __attribute__ ((visibility ("hidden"))) @interface RobTKPuglWindow : NSWindow { @public PuglView* puglview; } - (id) initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag; - (void) setPuglview:(PuglView*)view; - (BOOL) windowShouldClose:(id)sender; - (void) becomeKeyWindow:(id)sender; - (BOOL) canBecomeKeyWindow:(id)sender; @end @implementation RobTKPuglWindow - (id)initWithContentRect:(NSRect)contentRect styleMask:(unsigned int)aStyle backing:(NSBackingStoreType)bufferingType defer:(BOOL)flag { NSWindow* result = [super initWithContentRect:contentRect styleMask:aStyle backing:bufferingType defer:NO]; [result setAcceptsMouseMovedEvents:YES]; return (RobTKPuglWindow *)result; } - (void)setPuglview:(PuglView*)view { puglview = view; [self setContentSize:NSMakeSize(view->width, view->height) ]; } - (BOOL)windowShouldClose:(id)sender { if (puglview->closeFunc) puglview->closeFunc(puglview); return YES; } - (void)becomeKeyWindow:(id)sender { } - (BOOL) canBecomeKeyWindow:(id)sender{ // forward key-events return NO; } @end static void puglDisplay(PuglView* view) { if (view->displayFunc) { view->displayFunc(view); } } __attribute__ ((visibility ("hidden"))) @interface RobTKPuglOpenGLView : NSOpenGLView { @public PuglView* puglview; NSTrackingArea* trackingArea; } - (id) initWithFrame:(NSRect)frame; - (void) reshape; - (void) drawRect:(NSRect)rect; - (void) mouseMoved:(NSEvent*)event; - (void) mouseDragged:(NSEvent*)event; - (void) mouseDown:(NSEvent*)event; - (void) mouseUp:(NSEvent*)event; - (void) rightMouseDragged:(NSEvent*)event; - (void) rightMouseDown:(NSEvent*)event; - (void) rightMouseUp:(NSEvent*)event; - (void) otherMouseDown:(NSEvent*)event; - (void) otherMouseUp:(NSEvent*)event; - (void) mouseEntered:(NSEvent*)event; - (void) mouseExited:(NSEvent*)event; - (void) keyDown:(NSEvent*)event; - (void) keyUp:(NSEvent*)event; - (void) flagsChanged:(NSEvent*)event; @end @implementation RobTKPuglOpenGLView - (id) initWithFrame:(NSRect)frame { NSOpenGLPixelFormatAttribute pixelAttribs[16] = { NSOpenGLPFADoubleBuffer, NSOpenGLPFAAccelerated, NSOpenGLPFAColorSize, 32, NSOpenGLPFADepthSize, 32, NSOpenGLPFAMultisample, NSOpenGLPFASampleBuffers, 1, NSOpenGLPFASamples, 4, 0 }; NSOpenGLPixelFormat* pixelFormat = [[NSOpenGLPixelFormat alloc] initWithAttributes:pixelAttribs]; if (pixelFormat) { self = [super initWithFrame:frame pixelFormat:pixelFormat]; [pixelFormat release]; } else { self = [super initWithFrame:frame]; } if (self) { #ifdef ROBTK_UPSCALE if (macOS_Version () >= 11 /* 10.7 Lion */) { [self setWantsBestResolutionOpenGLSurface:YES]; [self setWantsLayer:YES]; } #else /* no upscale */ # if defined(MAC_OS_X_VERSION_10_15) && MAC_OS_X_VERSION_MAX_ALLOWED >= MAC_OS_X_VERSION_10_15 /* macOS 10.5 switched the default to YES (compile time) */ [self setWantsBestResolutionOpenGLSurface:NO]; # endif #endif [[self openGLContext] makeCurrentContext]; [self reshape]; [NSOpenGLContext clearCurrentContext]; } return self; } - (void) reshape { [super reshape]; [[self openGLContext] update]; NSRect bounds = [self bounds]; int width = bounds.size.width; int height = bounds.size.height; if (puglview) { /* NOTE: Apparently reshape gets called when the GC gets around to deleting the view (?), so we must have reset puglview to NULL when this comes around. */ [[self openGLContext] makeCurrentContext]; if (puglview->reshapeFunc) { puglview->reshapeFunc(puglview, width, height); } else { puglDefaultReshape(puglview, width, height); } [NSOpenGLContext clearCurrentContext]; puglview->width = width; puglview->height = height; } } - (void) drawRect:(NSRect)rect { [[self openGLContext] makeCurrentContext]; puglDisplay(puglview); glFlush(); glSwapAPPLE(); [NSOpenGLContext clearCurrentContext]; } static unsigned getModifiers(PuglView* view, NSEvent* ev) { const unsigned modifierFlags = [ev modifierFlags]; view->event_timestamp_ms = fmod([ev timestamp] * 1000.0, UINT32_MAX); unsigned mods = 0; mods |= (modifierFlags & NSShiftKeyMask) ? PUGL_MOD_SHIFT : 0; mods |= (modifierFlags & NSControlKeyMask) ? PUGL_MOD_CTRL : 0; mods |= (modifierFlags & NSAlternateKeyMask) ? PUGL_MOD_ALT : 0; mods |= (modifierFlags & NSCommandKeyMask) ? PUGL_MOD_SUPER : 0; return mods; } -(void)updateTrackingAreas { if (trackingArea != nil) { [self removeTrackingArea:trackingArea]; [trackingArea release]; } const int opts = (NSTrackingMouseEnteredAndExited | NSTrackingMouseMoved | NSTrackingActiveAlways); trackingArea = [ [NSTrackingArea alloc] initWithRect:[self bounds] options:opts owner:self userInfo:nil]; [self addTrackingArea:trackingArea]; [super updateTrackingAreas]; } - (void)mouseEntered:(NSEvent*)theEvent { if (puglview->focusFunc) { puglview->focusFunc(puglview, true); } } - (void)mouseExited:(NSEvent*)theEvent { if (puglview->focusFunc) { puglview->focusFunc(puglview, false); } } - (void) mouseMoved:(NSEvent*)event { if (puglview->motionFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); } } - (void) mouseDragged:(NSEvent*)event { if (puglview->motionFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); } } - (void) rightMouseDragged:(NSEvent*)event { if (puglview->motionFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->motionFunc(puglview, loc.x, puglview->height - loc.y); } } - (void) mouseDown:(NSEvent*)event { if (puglview->mouseFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->mouseFunc(puglview, 1, true, loc.x, puglview->height - loc.y); } } - (void) mouseUp:(NSEvent*)event { if (puglview->mouseFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->mouseFunc(puglview, 1, false, loc.x, puglview->height - loc.y); } } - (void) rightMouseDown:(NSEvent*)event { if (puglview->mouseFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->mouseFunc(puglview, 3, true, loc.x, puglview->height - loc.y); } } - (void) rightMouseUp:(NSEvent*)event { if (puglview->mouseFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->mouseFunc(puglview, 3, false, loc.x, puglview->height - loc.y); } } - (void) otherMouseDown:(NSEvent*)event { if (puglview->mouseFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->mouseFunc(puglview, [event buttonNumber], true, loc.x, puglview->height - loc.y); } } - (void) otherMouseUp:(NSEvent*)event { if (puglview->mouseFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->mouseFunc(puglview, [event buttonNumber], false, loc.x, puglview->height - loc.y); } } - (void) scrollWheel:(NSEvent*)event { if (puglview->scrollFunc) { NSPoint loc = [self convertPoint:[event locationInWindow] fromView:nil]; puglview->mods = getModifiers(puglview, event); puglview->scrollFunc(puglview, loc.x, puglview->height - loc.y, [event deltaX], [event deltaY]); } } - (void) keyDown:(NSEvent*)event { if (puglview->keyboardFunc && !(puglview->ignoreKeyRepeat && [event isARepeat])) { NSString* chars = [event characters]; puglview->mods = getModifiers(puglview, event); puglview->keyboardFunc(puglview, true, [chars characterAtIndex:0]); } } - (void) keyUp:(NSEvent*)event { if (puglview->keyboardFunc) { NSString* chars = [event characters]; puglview->mods = getModifiers(puglview, event); puglview->keyboardFunc(puglview, false, [chars characterAtIndex:0]); } } - (void) flagsChanged:(NSEvent*)event { if (puglview->specialFunc) { const unsigned mods = getModifiers(puglview, event); if ((mods & PUGL_MOD_SHIFT) != (puglview->mods & PUGL_MOD_SHIFT)) { puglview->specialFunc(puglview, mods & PUGL_MOD_SHIFT, PUGL_KEY_SHIFT); } else if ((mods & PUGL_MOD_CTRL) != (puglview->mods & PUGL_MOD_CTRL)) { puglview->specialFunc(puglview, mods & PUGL_MOD_CTRL, PUGL_KEY_CTRL); } else if ((mods & PUGL_MOD_ALT) != (puglview->mods & PUGL_MOD_ALT)) { puglview->specialFunc(puglview, mods & PUGL_MOD_ALT, PUGL_KEY_ALT); } else if ((mods & PUGL_MOD_SUPER) != (puglview->mods & PUGL_MOD_SUPER)) { puglview->specialFunc(puglview, mods & PUGL_MOD_SUPER, PUGL_KEY_SUPER); } puglview->mods = mods; } } @end struct PuglInternalsImpl { RobTKPuglOpenGLView* glview; id window; }; PuglView* puglCreate(PuglNativeWindow parent, const char* title, int min_width, int min_height, int width, int height, bool resizable, bool ontop, unsigned long transientId) { PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); if (!view || !impl) { return NULL; } view->impl = impl; view->width = width; view->height = height; view->ontop = ontop; view->ui_scale = 1.0; view->user_resizable = resizable; // unused [NSAutoreleasePool new]; [NSApplication sharedApplication]; impl->glview = [RobTKPuglOpenGLView new]; impl->glview->puglview = view; [impl->glview setFrameSize:NSMakeSize(view->width, view->height)]; if (parent) { NSView* pview = (NSView*) parent; [pview addSubview:impl->glview]; [impl->glview setHidden:NO]; if (!resizable) { [impl->glview setAutoresizingMask:NSViewNotSizable]; } } else { NSString* titleString = [[NSString alloc] initWithBytes:title length:strlen(title) encoding:NSUTF8StringEncoding]; NSRect frame = NSMakeRect(0, 0, min_width, min_height); NSUInteger style = NSClosableWindowMask | NSTitledWindowMask; if (resizable) { style |= NSResizableWindowMask; } id window = [[[RobTKPuglWindow alloc] initWithContentRect:frame styleMask:style backing:NSBackingStoreBuffered defer:NO ] retain]; [window setPuglview:view]; [window setTitle:titleString]; puglUpdateGeometryConstraints(view, min_width, min_height, min_width != width); if (ontop) { [window setLevel: NSStatusWindowLevel]; } impl->window = window; #if 0 if (resizable) { [impl->glview setAutoresizingMask:NSViewWidthSizable|NSViewHeightSizable]; } #endif [window setContentView:impl->glview]; [NSApp activateIgnoringOtherApps:YES]; [window makeFirstResponder:impl->glview]; [window makeKeyAndOrderFront:window]; } #ifdef ROBTK_UPSCALE struct ValueProxy { ValueProxy () : _f (1.0) {} void set_value (CGFloat f) { _f = f; } void set_value (id) { } float _f; } p; if (macOS_Version () >= 11 /* 10.7 Lion */) { if ([impl->glview window]) { p.set_value ([[impl->glview window] backingScaleFactor]); } else if (parent) { NSView* pview = (NSView*) parent; p.set_value ([[pview window] backingScaleFactor]); } else { p.set_value ([[NSScreen mainScreen] backingScaleFactor]); } if (p._f >= 1.f && p._f <= 4.f) { view->ui_scale = p._f; } } #endif return view; } void puglDestroy(PuglView* view) { if (!view) { return; } view->impl->glview->puglview = NULL; [view->impl->glview removeFromSuperview]; if (view->impl->window) { [view->impl->window close]; } [view->impl->glview release]; if (view->impl->window) { [view->impl->window release]; } free(view->impl); free(view); } PuglStatus puglProcessEvents(PuglView* view) { if (view->redisplay) { view->redisplay = false; [view->impl->glview setNeedsDisplay: YES]; } return PUGL_SUCCESS; } static void puglResize(PuglView* view) { int set_hints; // ignored view->resize = false; if (!view->resizeFunc) { return; } [[view->impl->glview openGLContext] makeCurrentContext]; view->resizeFunc(view, &view->width, &view->height, &set_hints); if (view->impl->window) { [view->impl->window setContentSize:NSMakeSize(view->width, view->height) ]; } else { [view->impl->glview setFrameSize:NSMakeSize(view->width, view->height)]; } [view->impl->glview reshape]; [NSOpenGLContext clearCurrentContext]; } void puglPostResize(PuglView* view) { view->resize = true; puglResize(view); } void puglShowWindow(PuglView* view) { [view->impl->window setIsVisible:YES]; } void puglHideWindow(PuglView* view) { [view->impl->window setIsVisible:NO]; } void puglPostRedisplay(PuglView* view) { view->redisplay = true; #ifdef USE_GUI_THREAD [view->impl->glview setNeedsDisplay: YES]; #endif } PuglNativeWindow puglGetNativeWindow(PuglView* view) { return (PuglNativeWindow)view->impl->glview; } int puglOpenFileDialog(PuglView* view, const char *title) { NSOpenPanel *panel = [NSOpenPanel openPanel]; [panel setCanChooseFiles:YES]; [panel setCanChooseDirectories:NO]; [panel setAllowsMultipleSelection:NO]; if (title) { NSString* titleString = [[NSString alloc] initWithBytes:title length:strlen(title) encoding:NSUTF8StringEncoding]; [panel setTitle:titleString]; } [panel beginWithCompletionHandler:^(NSInteger result) { bool file_ok = false; if (result == NSFileHandlingPanelOKButton) { for (NSURL *url in [panel URLs]) { if (![url isFileURL]) continue; //NSLog(@"%@", url.path); const char *fn= [url.path UTF8String]; file_ok = true; if (view->fileSelectedFunc) { view->fileSelectedFunc(view, fn); } break; } } if (!file_ok && view->fileSelectedFunc) { view->fileSelectedFunc(view, NULL); } }]; return 0; } bool rtk_osx_open_url (const char* url) { NSString* strurl = [[NSString alloc] initWithUTF8String:url]; NSURL* nsurl = [[NSURL alloc] initWithString:strurl]; bool ret = [[NSWorkspace sharedWorkspace] openURL:nsurl]; [strurl release]; [nsurl release]; return ret; } int puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) { [view->impl->window setContentMinSize:NSMakeSize(min_width, min_height)]; if (aspect) { [view->impl->window setContentAspectRatio:NSMakeSize(min_width, min_height)]; } return 0; } robtk-0.8.5/pugl/pugl_win.cpp000066400000000000000000000363271463170413500161630ustar00rootroot00000000000000/* Copyright 2012 David Robillard Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** @file pugl_win.cpp Windows/WGL Pugl Implementation. */ #include #include #include #include #include #include "pugl_internal.h" #ifndef WM_MOUSEWHEEL # define WM_MOUSEWHEEL 0x020A #endif #ifndef WM_MOUSEHWHEEL # define WM_MOUSEHWHEEL 0x020E #endif #ifndef WHEEL_DELTA # define WHEEL_DELTA 120 #endif #ifndef GWL_USERDATA # define GWL_USERDATA (-21) #endif const int LOCAL_CLOSE_MSG = WM_USER + 50; struct PuglInternalsImpl { HWND hwnd; HDC hdc; HGLRC hglrc; WNDCLASS wc; bool keep_aspect; int win_flags; }; LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam); PuglView* puglCreate(PuglNativeWindow parent, const char* title, int min_width, int min_height, int width, int height, bool resizable, bool ontop, unsigned long transientId) { PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); if (!view || !impl) { return NULL; } view->impl = impl; view->width = width; view->height = height; view->ontop = ontop; view->ui_scale = 1.0; view->user_resizable = resizable && !parent; view->impl->keep_aspect = min_width != width; // FIXME: This is nasty, and pugl should not have static anything. // Should class be a parameter? Does this make sense on other platforms? static int wc_count = 0; int retry = 99; char classNameBuf[256]; while (true) { _snprintf(classNameBuf, sizeof(classNameBuf), "x%d%s", wc_count++, title); classNameBuf[sizeof(classNameBuf)-1] = '\0'; impl->wc.style = CS_OWNDC; impl->wc.lpfnWndProc = wndProc; impl->wc.cbClsExtra = 0; impl->wc.cbWndExtra = 0; impl->wc.hInstance = 0; impl->wc.hIcon = LoadIcon(NULL, IDI_APPLICATION); impl->wc.hCursor = LoadCursor(NULL, IDC_ARROW); impl->wc.hbrBackground = (HBRUSH)GetStockObject(BLACK_BRUSH); impl->wc.lpszMenuName = NULL; impl->wc.lpszClassName = strdup(classNameBuf); if (RegisterClass(&impl->wc)) { break; } if (--retry > 0) { free((void*)impl->wc.lpszClassName); continue; } /* fail */ free((void*)impl->wc.lpszClassName); free(impl); free(view); return NULL; } if (parent) { view->impl->win_flags = WS_CHILD; } else { view->impl->win_flags = WS_POPUPWINDOW | WS_CAPTION | (view->user_resizable ? WS_SIZEBOX : 0); } // Adjust the overall window size to accomodate our requested client size RECT wr = { 0, 0, width, height }; AdjustWindowRectEx(&wr, view->impl->win_flags, FALSE, WS_EX_TOPMOST); RECT mr = { 0, 0, min_width, min_height }; AdjustWindowRectEx(&mr, view->impl->win_flags, FALSE, WS_EX_TOPMOST); view->min_width = mr.right - mr.left; view->min_height = mr.bottom - mr.top; impl->hwnd = CreateWindowEx ( WS_EX_TOPMOST, classNameBuf, title, (view->user_resizable ? WS_SIZEBOX : 0) | (parent ? (WS_CHILD | WS_VISIBLE) : (WS_POPUPWINDOW | WS_CAPTION)), 0, 0, wr.right - wr.left, wr.bottom - wr.top, (HWND)parent, NULL, NULL, NULL); if (!impl->hwnd) { free((void*)impl->wc.lpszClassName); free(impl); free(view); return NULL; } SetWindowLongPtr(impl->hwnd, GWL_USERDATA, (LONG_PTR)view); SetWindowPos (impl->hwnd, ontop ? HWND_TOPMOST : HWND_TOP, 0, 0, 0, 0, (ontop ? 0 : SWP_NOACTIVATE) | SWP_ASYNCWINDOWPOS | SWP_NOMOVE | SWP_NOSIZE); impl->hdc = GetDC(impl->hwnd); PIXELFORMATDESCRIPTOR pfd; ZeroMemory(&pfd, sizeof(pfd)); pfd.nSize = sizeof(pfd); pfd.nVersion = 1; pfd.dwFlags = PFD_DRAW_TO_WINDOW | PFD_SUPPORT_OPENGL | PFD_DOUBLEBUFFER; pfd.iPixelType = PFD_TYPE_RGBA; pfd.cColorBits = 24; pfd.cDepthBits = 16; pfd.iLayerType = PFD_MAIN_PLANE; int format = ChoosePixelFormat(impl->hdc, &pfd); SetPixelFormat(impl->hdc, format, &pfd); impl->hglrc = wglCreateContext(impl->hdc); if (!impl->hglrc) { ReleaseDC (impl->hwnd, impl->hdc); DestroyWindow (impl->hwnd); UnregisterClass (impl->wc.lpszClassName, NULL); free((void*)impl->wc.lpszClassName); free(impl); free(view); return NULL; } wglMakeCurrent(impl->hdc, impl->hglrc); view->width = width; view->height = height; return view; } void puglDestroy(PuglView* view) { if (!view) { return; } wglMakeCurrent(NULL, NULL); wglDeleteContext(view->impl->hglrc); ReleaseDC(view->impl->hwnd, view->impl->hdc); DestroyWindow(view->impl->hwnd); UnregisterClass(view->impl->wc.lpszClassName, NULL); free((void*)view->impl->wc.lpszClassName); free(view->impl); free(view); } PUGL_API void puglShowWindow(PuglView* view) { ShowWindow(view->impl->hwnd, WS_VISIBLE); ShowWindow(view->impl->hwnd, SW_RESTORE); //SetForegroundWindow(view->impl->hwnd); UpdateWindow(view->impl->hwnd); } PUGL_API void puglHideWindow(PuglView* view) { ShowWindow(view->impl->hwnd, SW_HIDE); UpdateWindow(view->impl->hwnd); } static void puglReshape(PuglView* view, int width, int height) { wglMakeCurrent(view->impl->hdc, view->impl->hglrc); if (view->reshapeFunc) { view->reshapeFunc(view, width, height); } else { puglDefaultReshape(view, width, height); } wglMakeCurrent(NULL, NULL); view->width = width; view->height = height; } static void puglDisplay(PuglView* view) { wglMakeCurrent(view->impl->hdc, view->impl->hglrc); #if 0 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); #endif view->redisplay = false; if (view->displayFunc) { view->displayFunc(view); } glFlush(); SwapBuffers(view->impl->hdc); wglMakeCurrent(NULL, NULL); } static void puglResize(PuglView* view) { int set_hints; // ignored for now view->resize = false; if (!view->resizeFunc) { return; } /* ask the plugin about the new size */ view->resizeFunc(view, &view->width, &view->height, &set_hints); HWND parent = GetParent (view->impl->hwnd); if (parent) { puglReshape(view, view->width, view->height); SetWindowPos (view->impl->hwnd, HWND_TOP, 0, 0, view->width, view->height, SWP_NOZORDER | SWP_NOMOVE); return; } RECT wr = { 0, 0, (long)view->width, (long)view->height }; AdjustWindowRectEx(&wr, view->impl->win_flags, FALSE, WS_EX_TOPMOST); SetWindowPos (view->impl->hwnd, view->ontop ? HWND_TOPMOST : HWND_NOTOPMOST, 0, 0, wr.right-wr.left, wr.bottom-wr.top, SWP_NOACTIVATE|SWP_NOMOVE|SWP_NOOWNERZORDER /*|SWP_NOZORDER*/); UpdateWindow(view->impl->hwnd); /* and call Reshape in GL context */ puglReshape(view, view->width, view->height); } static PuglKey keySymToSpecial(int sym) { switch (sym) { case VK_F1: return PUGL_KEY_F1; case VK_F2: return PUGL_KEY_F2; case VK_F3: return PUGL_KEY_F3; case VK_F4: return PUGL_KEY_F4; case VK_F5: return PUGL_KEY_F5; case VK_F6: return PUGL_KEY_F6; case VK_F7: return PUGL_KEY_F7; case VK_F8: return PUGL_KEY_F8; case VK_F9: return PUGL_KEY_F9; case VK_F10: return PUGL_KEY_F10; case VK_F11: return PUGL_KEY_F11; case VK_F12: return PUGL_KEY_F12; case VK_LEFT: return PUGL_KEY_LEFT; case VK_UP: return PUGL_KEY_UP; case VK_RIGHT: return PUGL_KEY_RIGHT; case VK_DOWN: return PUGL_KEY_DOWN; case VK_PRIOR: return PUGL_KEY_PAGE_UP; case VK_NEXT: return PUGL_KEY_PAGE_DOWN; case VK_HOME: return PUGL_KEY_HOME; case VK_END: return PUGL_KEY_END; case VK_INSERT: return PUGL_KEY_INSERT; case VK_SHIFT: return PUGL_KEY_SHIFT; case VK_CONTROL: return PUGL_KEY_CTRL; case VK_MENU: return PUGL_KEY_ALT; case VK_LWIN: return PUGL_KEY_SUPER; case VK_RWIN: return PUGL_KEY_SUPER; } return (PuglKey)0; } static void processMouseEvent(PuglView* view, int button, bool press, LPARAM lParam) { view->event_timestamp_ms = GetMessageTime(); if (GetFocus() != view->impl->hwnd) { // focus is needed to receive mouse-wheel events SetFocus (view->impl->hwnd); } if (press) { SetCapture(view->impl->hwnd); } else { ReleaseCapture(); } if (view->mouseFunc) { view->mouseFunc(view, button, press, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); } } static void setModifiers(PuglView* view) { view->mods = 0; view->mods |= (GetKeyState(VK_SHIFT) < 0) ? PUGL_MOD_SHIFT : 0; view->mods |= (GetKeyState(VK_CONTROL) < 0) ? PUGL_MOD_CTRL : 0; view->mods |= (GetKeyState(VK_MENU) < 0) ? PUGL_MOD_ALT : 0; view->mods |= (GetKeyState(VK_LWIN) < 0) ? PUGL_MOD_SUPER : 0; view->mods |= (GetKeyState(VK_RWIN) < 0) ? PUGL_MOD_SUPER : 0; } static LRESULT handleMessage(PuglView* view, UINT message, WPARAM wParam, LPARAM lParam) { PAINTSTRUCT ps; PuglKey key; setModifiers(view); switch (message) { case WM_CREATE: case WM_SHOWWINDOW: case WM_SIZE: { RECT rect; GetClientRect(view->impl->hwnd, &rect); puglReshape(view, rect.right, rect.bottom); view->width = rect.right; view->height = rect.bottom; } break; case WM_SIZING: if (view->impl->keep_aspect) { float aspect = view->min_width / (float)view->min_height; RECT* rect = (RECT*)lParam; switch ((int)wParam) { case WMSZ_LEFT: case WMSZ_RIGHT: case WMSZ_BOTTOMLEFT: case WMSZ_BOTTOMRIGHT: rect->bottom = rect->top + (rect->right - rect->left) / aspect; break; case WMSZ_TOP: case WMSZ_BOTTOM: case WMSZ_TOPRIGHT: rect->right = rect->left + (rect->bottom - rect->top) * aspect; break; case WMSZ_TOPLEFT: rect->left = rect->right - (rect->bottom - rect->top) * aspect; break; } return TRUE; } break; case WM_GETMINMAXINFO: { MINMAXINFO *mmi = (MINMAXINFO*)lParam; mmi->ptMinTrackSize.x = view->min_width; mmi->ptMinTrackSize.y = view->min_height; } break; case WM_PAINT: BeginPaint(view->impl->hwnd, &ps); puglDisplay(view); EndPaint(view->impl->hwnd, &ps); break; case WM_MOUSEMOVE: if (view->motionFunc) { view->motionFunc(view, GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam)); } break; case WM_LBUTTONDOWN: processMouseEvent(view, 1, true, lParam); break; case WM_MBUTTONDOWN: processMouseEvent(view, 2, true, lParam); break; case WM_RBUTTONDOWN: processMouseEvent(view, 3, true, lParam); break; case WM_LBUTTONUP: processMouseEvent(view, 1, false, lParam); break; case WM_MBUTTONUP: processMouseEvent(view, 2, false, lParam); break; case WM_RBUTTONUP: processMouseEvent(view, 3, false, lParam); break; case WM_MOUSEWHEEL: if (view->scrollFunc) { view->event_timestamp_ms = GetMessageTime(); POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(view->impl->hwnd, &pt); view->scrollFunc( view, pt.x, pt.y, 0, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA); } break; case WM_MOUSEHWHEEL: if (view->scrollFunc) { view->event_timestamp_ms = GetMessageTime(); POINT pt = { GET_X_LPARAM(lParam), GET_Y_LPARAM(lParam) }; ScreenToClient(view->impl->hwnd, &pt); view->scrollFunc( view, pt.x, pt.y, GET_WHEEL_DELTA_WPARAM(wParam) / (float)WHEEL_DELTA, 0); } break; case WM_KEYDOWN: if (view->ignoreKeyRepeat && (lParam & (1 << 30))) { break; } // else nobreak case WM_KEYUP: view->event_timestamp_ms = GetMessageTime(); if ((key = keySymToSpecial(wParam))) { if (view->specialFunc) { view->specialFunc(view, message == WM_KEYDOWN, key); } } else if (view->keyboardFunc) { static BYTE kbs[256]; if (GetKeyboardState(kbs) != FALSE) { char lb[2]; UINT scanCode = (lParam >> 8) & 0xFFFFFF00; if ( 1 == ToAscii(wParam, scanCode, kbs, (LPWORD)lb, 0)) { view->keyboardFunc(view, message == WM_KEYDOWN, (char)lb[0]); } } } break; case WM_SETFOCUS: if (view->focusFunc) { view->focusFunc(view, TRUE); } return DefWindowProc(view->impl->hwnd, message, wParam, lParam); case WM_KILLFOCUS: if (view->focusFunc) { view->focusFunc(view, FALSE); } return DefWindowProc(view->impl->hwnd, message, wParam, lParam); case WM_QUIT: case LOCAL_CLOSE_MSG: if (view->closeFunc) { view->closeFunc(view); } break; default: return DefWindowProc( view->impl->hwnd, message, wParam, lParam); } return 0; } PuglStatus puglProcessEvents(PuglView* view) { MSG msg; while (PeekMessage(&msg, view->impl->hwnd, 0, 0, PM_REMOVE)) { handleMessage(view, msg.message, msg.wParam, msg.lParam); } if (view->resize) { puglResize(view); } if (view->redisplay) { InvalidateRect(view->impl->hwnd, NULL, FALSE); } return PUGL_SUCCESS; } LRESULT CALLBACK wndProc(HWND hwnd, UINT message, WPARAM wParam, LPARAM lParam) { PuglView* view = (PuglView*)GetWindowLongPtr(hwnd, GWL_USERDATA); switch (message) { case WM_CREATE: PostMessage(hwnd, WM_SHOWWINDOW, TRUE, 0); return 0; case WM_CLOSE: PostMessage(hwnd, LOCAL_CLOSE_MSG, wParam, lParam); return 0; case WM_DESTROY: return 0; default: if (view && hwnd == view->impl->hwnd) { return handleMessage(view, message, wParam, lParam); } else { return DefWindowProc(hwnd, message, wParam, lParam); } } } void puglPostRedisplay(PuglView* view) { view->redisplay = true; } void puglPostResize(PuglView* view) { view->resize = true; } PuglNativeWindow puglGetNativeWindow(PuglView* view) { return (PuglNativeWindow)view->impl->hwnd; } int puglOpenFileDialog(PuglView* view, const char *title) { char fn[1024] = ""; OPENFILENAME ofn; ZeroMemory(&ofn, sizeof(OPENFILENAME)); ofn.lStructSize = sizeof(OPENFILENAME); ofn.lpstrFile = fn; ofn.nMaxFile = 1024; ofn.lpstrTitle = title; ofn.lpstrFilter = "All\0*.*\0"; ofn.nFilterIndex = 0; ofn.lpstrInitialDir = 0; ofn.Flags = OFN_ENABLESIZING | OFN_FILEMUSTEXIST | OFN_NONETWORKBUTTON | OFN_HIDEREADONLY | OFN_READONLY; // TODO look into async ofn.lpfnHook, OFN_ENABLEHOOK // UINT_PTR CALLBACK openFileHook(HWND hDlg, UINT message, WPARAM wParam, LPARAM lParam) // yet it seems GetOpenFIleName itself won't return anyway. ofn.hwndOwner = view->impl->hwnd; // modal if (GetOpenFileName (&ofn)) { if (view->fileSelectedFunc) { view->fileSelectedFunc(view, fn); } } else { if (view->fileSelectedFunc) { view->fileSelectedFunc(view, NULL); } } return 0; } int puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) { view->min_width = min_width; view->min_height = min_height; if (view->width < min_width || view->height < min_height) { if (!view->resize) { if (view->width < min_width) view->width = min_width; if (view->height < min_height) view->height = min_height; view->resize = true; // TODO: trigger resize // (not always needed since this fn is usually called in response to a resize) } } return 0; } robtk-0.8.5/pugl/pugl_x11.c000066400000000000000000000400051463170413500154230ustar00rootroot00000000000000/* Copyright 2012 David Robillard Copyright 2011-2012 Ben Loftis, Harrison Consoles Copyright 2013,2015 Robin Gareus Permission to use, copy, modify, and/or distribute this software for any purpose with or without fee is hereby granted, provided that the above copyright notice and this permission notice appear in all copies. THIS SOFTWARE IS PROVIDED "AS IS" AND THE AUTHOR DISCLAIMS ALL WARRANTIES WITH REGARD TO THIS SOFTWARE INCLUDING ALL IMPLIED WARRANTIES OF MERCHANTABILITY AND FITNESS. IN NO EVENT SHALL THE AUTHOR BE LIABLE FOR ANY SPECIAL, DIRECT, INDIRECT, OR CONSEQUENTIAL DAMAGES OR ANY DAMAGES WHATSOEVER RESULTING FROM LOSS OF USE, DATA OR PROFITS, WHETHER IN AN ACTION OF CONTRACT, NEGLIGENCE OR OTHER TORTIOUS ACTION, ARISING OUT OF OR IN CONNECTION WITH THE USE OR PERFORMANCE OF THIS SOFTWARE. */ /** @file pugl_x11.c X11 Pugl Implementation. */ #include #include #include #include #include #include #include #include #include "pugl_internal.h" #ifdef WITH_SOFD #define HAVE_X11 #include "../sofd/libsofd.h" #include "../sofd/libsofd.c" #endif /* work around buggy re-parent & focus issues on some systems * where no keyboard events are passed through even if the * app has mouse-focus and all other events are working. */ //#define XKEYFOCUSGRAB /* show messages during initalization */ //#define VERBOSE_PUGL struct PuglInternalsImpl { Display* display; int screen; Window win; GLXContext ctx; Bool doubleBuffered; }; /** Attributes for single-buffered RGBA with at least 4 bits per color and a 16 bit depth buffer. */ static int attrListSgl[] = { GLX_RGBA, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, GLX_ARB_multisample, 1, None }; /** Attributes for double-buffered RGBA with at least 4 bits per color and a 16 bit depth buffer. */ static int attrListDbl[] = { GLX_RGBA, GLX_DOUBLEBUFFER, GLX_RED_SIZE, 4, GLX_GREEN_SIZE, 4, GLX_BLUE_SIZE, 4, GLX_DEPTH_SIZE, 16, GLX_ARB_multisample, 1, None }; /** Attributes for double-buffered RGBA with multi-sampling (antialiasing) */ static int attrListDblMS[] = { GLX_RGBA, GLX_DOUBLEBUFFER , True, GLX_RED_SIZE , 4, GLX_GREEN_SIZE , 4, GLX_BLUE_SIZE , 4, GLX_ALPHA_SIZE , 4, GLX_DEPTH_SIZE , 16, GLX_SAMPLE_BUFFERS , 1, GLX_SAMPLES , 4, None }; PuglView* puglCreate(PuglNativeWindow parent, const char* title, int min_width, int min_height, int width, int height, bool resizable, bool ontop, unsigned long transientId) { PuglView* view = (PuglView*)calloc(1, sizeof(PuglView)); PuglInternals* impl = (PuglInternals*)calloc(1, sizeof(PuglInternals)); if (!view || !impl) { free(view); free(impl); return NULL; } view->impl = impl; view->width = width; view->height = height; view->ontop = ontop; view->ui_scale = 1.0; view->set_window_hints = true; view->user_resizable = resizable; impl->display = XOpenDisplay(0); if (!impl->display) { free(view); free(impl); return 0; } impl->screen = DefaultScreen(impl->display); impl->doubleBuffered = True; XVisualInfo* vi = glXChooseVisual(impl->display, impl->screen, attrListDblMS); if (!vi) { vi = glXChooseVisual(impl->display, impl->screen, attrListDbl); #ifdef VERBOSE_PUGL printf("puGL: multisampling (antialiasing) is not available\n"); #endif } if (!vi) { vi = glXChooseVisual(impl->display, impl->screen, attrListSgl); impl->doubleBuffered = False; #ifdef VERBOSE_PUGL printf("puGL: singlebuffered rendering will be used, no doublebuffering available\n"); #endif } if (!vi) { XCloseDisplay (impl->display); free(view); free(impl); return 0; } int glxMajor, glxMinor; glXQueryVersion(impl->display, &glxMajor, &glxMinor); #ifdef VERBOSE_PUGL printf("puGL: GLX-Version : %d.%d\n", glxMajor, glxMinor); #endif impl->ctx = glXCreateContext(impl->display, vi, 0, GL_TRUE); if (!impl->ctx) { XCloseDisplay (impl->display); free(view); free(impl); return 0; } Window xParent = parent ? (Window)parent : RootWindow(impl->display, impl->screen); Colormap cmap = XCreateColormap( impl->display, xParent, vi->visual, AllocNone); XSetWindowAttributes attr; memset(&attr, 0, sizeof(XSetWindowAttributes)); attr.colormap = cmap; attr.border_pixel = 0; attr.event_mask = ExposureMask | KeyPressMask | KeyReleaseMask | ButtonPressMask | ButtonReleaseMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask; impl->win = XCreateWindow( impl->display, xParent, 0, 0, view->width, view->height, 0, vi->depth, InputOutput, vi->visual, CWBorderPixel | CWColormap | CWEventMask, &attr); if (!impl->win) { free(view); free(impl); return 0; } XFlush(view->impl->display); XResizeWindow(view->impl->display, view->impl->win, width, height); if (min_width != width) { /* only call if aspect ration is to be set */ puglUpdateGeometryConstraints(view, min_width, min_height, min_width != width); } if (title) { XStoreName(impl->display, impl->win, title); } if (!parent) { Atom wmDelete = XInternAtom(impl->display, "WM_DELETE_WINDOW", True); XSetWMProtocols(impl->display, impl->win, &wmDelete, 1); } if (!parent && view->ontop) { /* TODO stay on top */ Atom type = XInternAtom(impl->display, "_NET_WM_STATE_ABOVE", False); XChangeProperty(impl->display, impl->win, XInternAtom(impl->display, "_NET_WM_STATE", False), XInternAtom(impl->display, "ATOM", False), 32, PropModeReplace, (unsigned char *)&type, 1); } if (transientId > 0) { XSetTransientForHint(impl->display, impl->win, (Window)(transientId)); } if (parent) { XMapRaised(impl->display, impl->win); } if (glXIsDirect(impl->display, impl->ctx)) { #ifdef VERBOSE_PUGL printf("puGL: DRI enabled\n"); #endif } else { #ifdef VERBOSE_PUGL printf("puGL: No DRI available\n"); #endif } XFlush(view->impl->display); XFree(vi); return view; } void puglDestroy(PuglView* view) { if (!view) { return; } #ifdef WITH_SOFD x_fib_close(view->impl->display); #endif //glXMakeCurrent(view->impl->display, None, NULL); glXDestroyContext(view->impl->display, view->impl->ctx); XDestroyWindow(view->impl->display, view->impl->win); XCloseDisplay(view->impl->display); free(view->impl); free(view); view = NULL; } PUGL_API void puglShowWindow(PuglView* view) { XMapRaised(view->impl->display, view->impl->win); } PUGL_API void puglHideWindow(PuglView* view) { XUnmapWindow(view->impl->display, view->impl->win); } static void puglReshape(PuglView* view, int width, int height) { glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); if (view->reshapeFunc) { view->reshapeFunc(view, width, height); } else { puglDefaultReshape(view, width, height); } glXMakeCurrent(view->impl->display, None, NULL); view->width = width; view->height = height; } static void puglDisplay(PuglView* view) { glXMakeCurrent(view->impl->display, view->impl->win, view->impl->ctx); #if 0 glClear(GL_COLOR_BUFFER_BIT | GL_DEPTH_BUFFER_BIT); glLoadIdentity(); #endif view->redisplay = false; if (view->displayFunc) { view->displayFunc(view); } glFlush(); if (view->impl->doubleBuffered) { glXSwapBuffers(view->impl->display, view->impl->win); } glXMakeCurrent(view->impl->display, None, NULL); } static void puglResize(PuglView* view) { int set_hints = 1; view->resize = false; if (!view->resizeFunc) { return; } /* ask the plugin about the new size */ view->resizeFunc(view, &view->width, &view->height, &set_hints); XSizeHints *hints = XAllocSizeHints(); hints->min_width = view->width; hints->min_height = view->height; hints->max_width = view->user_resizable ? 2048 : view->width; hints->max_height = view->user_resizable ? 2048 : view->height; hints->flags = PMaxSize | PMinSize; if (set_hints) { #ifdef VERBOSE_PUGL printf ("puGL: set hints. min-size %d x %d\n", hints->min_width, hints->min_height); #endif XSetWMNormalHints(view->impl->display, view->impl->win, hints); } XResizeWindow(view->impl->display, view->impl->win, view->width, view->height); XFlush(view->impl->display); XFree(hints); #ifdef VERBOSE_PUGL printf("puGL: window resize (%dx%d)\n", view->width, view->height); #endif /* and call Reshape in glX context */ puglReshape(view, view->width, view->height); } static PuglKey keySymToSpecial(KeySym sym) { switch (sym) { case XK_F1: return PUGL_KEY_F1; case XK_F2: return PUGL_KEY_F2; case XK_F3: return PUGL_KEY_F3; case XK_F4: return PUGL_KEY_F4; case XK_F5: return PUGL_KEY_F5; case XK_F6: return PUGL_KEY_F6; case XK_F7: return PUGL_KEY_F7; case XK_F8: return PUGL_KEY_F8; case XK_F9: return PUGL_KEY_F9; case XK_F10: return PUGL_KEY_F10; case XK_F11: return PUGL_KEY_F11; case XK_F12: return PUGL_KEY_F12; case XK_Left: return PUGL_KEY_LEFT; case XK_Up: return PUGL_KEY_UP; case XK_Right: return PUGL_KEY_RIGHT; case XK_Down: return PUGL_KEY_DOWN; case XK_Page_Up: return PUGL_KEY_PAGE_UP; case XK_Page_Down: return PUGL_KEY_PAGE_DOWN; case XK_Home: return PUGL_KEY_HOME; case XK_End: return PUGL_KEY_END; case XK_Insert: return PUGL_KEY_INSERT; case XK_Shift_L: return PUGL_KEY_SHIFT; case XK_Shift_R: return PUGL_KEY_SHIFT; case XK_Control_L: return PUGL_KEY_CTRL; case XK_Control_R: return PUGL_KEY_CTRL; case XK_Alt_L: return PUGL_KEY_ALT; case XK_Alt_R: return PUGL_KEY_ALT; case XK_Super_L: return PUGL_KEY_SUPER; case XK_Super_R: return PUGL_KEY_SUPER; } return (PuglKey)0; } static void setModifiers(PuglView* view, unsigned xstate, unsigned xtime) { view->event_timestamp_ms = xtime; view->mods = 0; view->mods |= (xstate & ShiftMask) ? PUGL_MOD_SHIFT : 0; view->mods |= (xstate & ControlMask) ? PUGL_MOD_CTRL : 0; view->mods |= (xstate & Mod1Mask) ? PUGL_MOD_ALT : 0; view->mods |= (xstate & Mod4Mask) ? PUGL_MOD_SUPER : 0; } Window x_fib_window(); PuglStatus puglProcessEvents(PuglView* view) { XEvent event; while (XPending(view->impl->display) > 0) { XNextEvent(view->impl->display, &event); #ifdef WITH_SOFD if (x_fib_handle_events(view->impl->display, &event)) { const int status = x_fib_status(); if (status > 0) { char* const filename = x_fib_filename(); x_fib_close(view->impl->display); x_fib_add_recent (filename, time(NULL)); //x_fib_save_recent ("~/.robtk.recent"); if (view->fileSelectedFunc) { view->fileSelectedFunc(view, filename); } free(filename); x_fib_free_recent (); } else if (status < 0) { x_fib_close(view->impl->display); if (view->fileSelectedFunc) { view->fileSelectedFunc(view, NULL); } } } #endif if (event.xany.window != view->impl->win) { continue; } switch (event.type) { case UnmapNotify: if (view->motionFunc) { view->motionFunc(view, -1, -1); } break; case MapNotify: puglReshape(view, view->width, view->height); break; case ConfigureNotify: if ((event.xconfigure.width != view->width) || (event.xconfigure.height != view->height)) { puglReshape(view, event.xconfigure.width, event.xconfigure.height); } break; case Expose: if (event.xexpose.count != 0) { break; } puglDisplay(view); break; case MotionNotify: setModifiers(view, event.xmotion.state, event.xmotion.time); if (view->motionFunc) { view->motionFunc(view, event.xmotion.x, event.xmotion.y); } break; case ButtonPress: setModifiers(view, event.xbutton.state, event.xbutton.time); if (event.xbutton.button >= 4 && event.xbutton.button <= 7) { if (view->scrollFunc) { float dx = 0, dy = 0; switch (event.xbutton.button) { case 4: dy = 1.0f; break; case 5: dy = -1.0f; break; case 6: dx = -1.0f; break; case 7: dx = 1.0f; break; } view->scrollFunc(view, event.xbutton.x, event.xbutton.y, dx, dy); } break; } // nobreak case ButtonRelease: setModifiers(view, event.xbutton.state, event.xbutton.time); if (view->mouseFunc && (event.xbutton.button < 4 || event.xbutton.button > 7)) { view->mouseFunc(view, event.xbutton.button, event.type == ButtonPress, event.xbutton.x, event.xbutton.y); } break; case KeyPress: { setModifiers(view, event.xkey.state, event.xkey.time); KeySym sym; char str[5]; int n = XLookupString(&event.xkey, str, 4, &sym, NULL); PuglKey key = keySymToSpecial(sym); if (!key && view->keyboardFunc) { if (n == 1) { view->keyboardFunc(view, true, str[0]); } else { fprintf(stderr, "warning: Unknown key %X\n", (int)sym); } } else if (view->specialFunc) { view->specialFunc(view, true, key); } } break; case KeyRelease: { setModifiers(view, event.xkey.state, event.xkey.time); bool repeated = false; if (view->ignoreKeyRepeat && XEventsQueued(view->impl->display, QueuedAfterReading)) { XEvent next; XPeekEvent(view->impl->display, &next); if (next.type == KeyPress && next.xkey.time == event.xkey.time && next.xkey.keycode == event.xkey.keycode) { XNextEvent(view->impl->display, &event); repeated = true; } } if (!repeated) { KeySym sym = XLookupKeysym(&event.xkey, 0); PuglKey special = keySymToSpecial(sym); #if 0 // close on 'Esc' if (sym == XK_Escape && view->closeFunc) { view->closeFunc(view); view->redisplay = false; } else #endif if (view->keyboardFunc) { if (!special) { view->keyboardFunc(view, false, sym); } else if (view->specialFunc) { view->specialFunc(view, false, special); } } } } break; case ClientMessage: { char* type = XGetAtomName(view->impl->display, event.xclient.message_type); if (!strcmp(type, "WM_PROTOCOLS")) { if (view->closeFunc) { view->closeFunc(view); view->redisplay = false; } } XFree(type); } break; case EnterNotify: #ifdef XKEYFOCUSGRAB XSetInputFocus(view->impl->display, view->impl->win, RevertToPointerRoot, CurrentTime); #endif if (view->focusFunc) { view->focusFunc(view, true); } break; case LeaveNotify: if (view->focusFunc) { view->focusFunc(view, false); } break; default: break; } } if (view->resize) { puglResize(view); } if (view->redisplay) { puglDisplay(view); } return PUGL_SUCCESS; } void puglPostRedisplay(PuglView* view) { view->redisplay = true; } void puglPostResize(PuglView* view) { view->resize = true; } PuglNativeWindow puglGetNativeWindow(PuglView* view) { return view->impl->win; } int puglOpenFileDialog(PuglView* view, const char *title) { #ifdef WITH_SOFD //x_fib_cfg_filter_callback (fib_filter_movie_filename); if (x_fib_configure (1, title)) { return -1; } //x_fib_load_recent ("~/.robtk.recent"); if (x_fib_show (view->impl->display, view->impl->win, 300, 300)) { return -1; } return 0; #else return -1; #endif } int puglUpdateGeometryConstraints(PuglView* view, int min_width, int min_height, bool aspect) { if (!view->set_window_hints) { return -1; } #ifdef VERBOSE_PUGL printf ("puGL UpdateGeometryConstraints %d x %d aspect:%d\n", min_width, min_height, aspect); #endif XSizeHints sizeHints; memset(&sizeHints, 0, sizeof(sizeHints)); sizeHints.flags = PMinSize|PMaxSize; sizeHints.min_width = min_width; sizeHints.min_height = min_height; sizeHints.max_width = view->user_resizable ? 2048 : min_width; sizeHints.max_height = view->user_resizable ? 2048 : min_height; if (aspect) { sizeHints.flags |= PAspect; sizeHints.min_aspect.x=min_width; sizeHints.min_aspect.y=min_height; sizeHints.max_aspect.x=min_width; sizeHints.max_aspect.y=min_height; } XSetWMNormalHints(view->impl->display, view->impl->win, &sizeHints); XFlush(view->impl->display); return 0; } robtk-0.8.5/robtk.h000066400000000000000000000237461463170413500141570ustar00rootroot00000000000000/* robwidget - gtk2 & GL wrapper * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROBTK_H_ #define _ROBTK_H_ #ifndef VERSION #define VERSION "0.invalid" #endif #include #include #include #include #include #include #ifdef HAVE_LV2_1_18_6 #include #include #include #include #else #include #include #include #include #endif #ifdef __APPLE__ #include #include #endif #ifdef _WIN32 static LARGE_INTEGER getFILETIMEoffset() { SYSTEMTIME s; FILETIME f; LARGE_INTEGER t; s.wYear = 1970; s.wMonth = 1; s.wDay = 1; s.wHour = 0; s.wMinute = 0; s.wSecond = 0; s.wMilliseconds = 0; SystemTimeToFileTime(&s, &f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; return (t); } #endif // monotonic time static void rtk_clock_gettime(struct timespec *ts) { #ifdef __APPLE__ clock_serv_t cclock; mach_timespec_t mts; host_get_clock_service(mach_host_self(), SYSTEM_CLOCK /*CALENDAR_CLOCK*/, &cclock); clock_get_time(cclock, &mts); mach_port_deallocate(mach_task_self(), cclock); ts->tv_sec = mts.tv_sec; ts->tv_nsec = mts.tv_nsec; #elif defined _WIN32 LARGE_INTEGER t; FILETIME f; double microseconds; static LARGE_INTEGER offset; static double frequencyToMicroseconds; static int initialized = 0; static BOOL usePerformanceCounter = 0; if (!initialized) { LARGE_INTEGER performanceFrequency; initialized = 1; usePerformanceCounter = QueryPerformanceFrequency(&performanceFrequency); if (usePerformanceCounter) { QueryPerformanceCounter(&offset); frequencyToMicroseconds = (double)performanceFrequency.QuadPart / 1000000.; } else { offset = getFILETIMEoffset(); frequencyToMicroseconds = 10.; } } if (usePerformanceCounter) QueryPerformanceCounter(&t); else { GetSystemTimeAsFileTime(&f); t.QuadPart = f.dwHighDateTime; t.QuadPart <<= 32; t.QuadPart |= f.dwLowDateTime; } t.QuadPart -= offset.QuadPart; microseconds = (double)t.QuadPart / frequencyToMicroseconds; t.QuadPart = microseconds; ts->tv_sec = t.QuadPart / 1000000; ts->tv_nsec = 1000 * (t.QuadPart % 1000000); #else clock_gettime(CLOCK_MONOTONIC, ts); #endif } // realtime time -- only used for pthread_cond_timedwait() static void rtk_clock_systime(struct timespec *ts) { #ifdef __APPLE__ rtk_clock_gettime(ts); #elif defined _WIN32 struct __timeb64 currSysTime; _ftime64(&currSysTime); ts->tv_sec = currSysTime.time; ts->tv_nsec = 1000 * currSysTime.millitm; #else clock_gettime(CLOCK_REALTIME, ts); #endif } /*****************************************************************************/ typedef struct { int x; int y; int state; int direction; // scroll int button; } RobTkBtnEvent; #ifndef ROBTK_MOD_SHIFT #error backend implementation misses ROBTK_MOD_SHIFT #endif #ifndef ROBTK_MOD_CTRL #error backend implementation misses ROBTK_MOD_CTRL #endif enum { ROBTK_SCROLL_ZERO, ROBTK_SCROLL_UP, ROBTK_SCROLL_DOWN, ROBTK_SCROLL_LEFT, ROBTK_SCROLL_RIGHT }; enum LVGLResize { LVGL_ZOOM_TO_ASPECT, LVGL_LAYOUT_TO_FIT, LVGL_CENTER, LVGL_TOP_LEFT, }; typedef struct _robwidget { void *self; // user-handle for the actual (wrapped) widget /* required */ bool (*expose_event) (struct _robwidget* handle, cairo_t* cr, cairo_rectangle_t *ev); void (*size_request) (struct _robwidget* handle, int *w, int *h); /* optional */ void (*position_set) (struct _robwidget* handle, int pw, int ph); void (*size_allocate) (struct _robwidget* handle, int pw, int ph); /* optional -- hybrid GL+cairo scaling */ void (*size_limit) (struct _robwidget* handle, int *pw, int *ph); void (*size_default) (struct _robwidget* handle, int *pw, int *ph); /* optional */ struct _robwidget* (*mousedown) (struct _robwidget*, RobTkBtnEvent *event); struct _robwidget* (*mouseup) (struct _robwidget*, RobTkBtnEvent *event); struct _robwidget* (*mousemove) (struct _robwidget*, RobTkBtnEvent *event); struct _robwidget* (*mousescroll) (struct _robwidget*, RobTkBtnEvent *event); void (*enter_notify) (struct _robwidget*); void (*leave_notify) (struct _robwidget*); /* internal - GL */ #ifndef GTK_BACKEND void* top; struct _robwidget* parent; struct _robwidget **children; unsigned int childcount; float widget_scale; bool redraw_pending; // queue_draw_*() failed (during init or top-levelresize) bool resized; // full-redraw --containers after resize bool hidden; // don't display, skip in layout and events int packing_opts; bool block_events; #endif float xalign, yalign; // unused in GTK cairo_rectangle_t area; // allocated pos + size cairo_rectangle_t trel; // cached pos + size relative to top widget bool cached_position; /* internal - GTK */ #ifdef GTK_BACKEND GtkWidget *m0; GtkWidget *c; #endif char name[12]; // debug with ROBWIDGET_NAME() } RobWidget; /*****************************************************************************/ /* provided by host */ static void resize_self(RobWidget *rw); // dangerous :) -- never call from expose_event static void resize_toplevel(RobWidget *rw, int w, int h); // ditto static void relayout_toplevel(RobWidget *rw); static void queue_draw(RobWidget *); static void queue_draw_area(RobWidget *, int, int, int, int); static void queue_tiny_area(RobWidget *rw, float x, float y, float w, float h); static void robwidget_toplevel_enable_scaling (RobWidget* rw, void (*cb) (RobWidget* w, void* h), void* handle); static void robtk_queue_scale_change (RobWidget *rw, const float ws); static RobWidget * robwidget_new(void *); static void robwidget_destroy(RobWidget *rw); static const char * robtk_info(void *); /*****************************************************************************/ /* static plugin singletons */ static LV2UI_Handle instantiate( void *const ui_toplevel, const LV2UI_Descriptor* descriptor, const char* plugin_uri, const char* bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, RobWidget** widget, const LV2_Feature* const* features); static void cleanup(LV2UI_Handle handle); static enum LVGLResize plugin_scale_mode(LV2UI_Handle handle); static void port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void* buffer); static const void * extension_data(const char* uri); #define ROBWIDGET_NAME(RW) ( ((RobWidget*)(RW))->name[0] ? (const char*) (((RobWidget*)(RW))->name) : (const char*) "???") #define ROBWIDGET_SETNAME(RW, TXT) strcpy(((RobWidget*)(RW))->name, TXT); // max 11 chars /******************************************************************************/ // utils // static void rect_intersection(cairo_rectangle_t *r, const cairo_rectangle_t *r1, const cairo_rectangle_t *r2){ cairo_rectangle_t is; is.x = MAX(r1->x, r2->x); is.y = MAX(r1->y, r2->y); is.width = MAX(0, MIN(r1->x + r1->width, r2->x + r2->width) - MAX(r1->x, r2->x)); is.height = MAX(0, MIN(r1->y + r1->height, r2->y + r2->height) - MAX(r1->y, r2->y)); memcpy(r, &is, sizeof(cairo_rectangle_t)); } static bool rect_intersect(const cairo_rectangle_t *r1, const cairo_rectangle_t *r2){ float dest_x, dest_y; float dest_x2, dest_y2; dest_x = MAX (r1->x, r2->x); dest_y = MAX (r1->y, r2->y); dest_x2 = MIN (r1->x + r1->width, r2->x + r2->width); dest_y2 = MIN (r1->y + r1->height, r2->y + r2->height); return (dest_x2 > dest_x && dest_y2 > dest_y); } static bool rect_intersect_a(const cairo_rectangle_t *r1, const float x, const float y, const float w, const float h) { cairo_rectangle_t r2 = { x, y, w, h}; return rect_intersect(r1, &r2); } static void rect_combine(const cairo_rectangle_t *r1, const cairo_rectangle_t *r2, cairo_rectangle_t *dest) { double dest_x, dest_y; dest_x = MIN (r1->x, r2->x); dest_y = MIN (r1->y, r2->y); dest->width = MAX (r1->x + r1->width, r2->x + r2->width) - dest_x; dest->height = MAX (r1->y + r1->height, r2->y + r2->height) - dest_y; dest->x = dest_x; dest->y = dest_y; } static void get_color_from_theme (int which, float *col); #include "rtk/common.h" #include "rtk/style.h" #ifdef GTK_BACKEND #include "gtk2/common_cgtk.h" #include "gtk2/robwidget_gtk.h" #else #include "gl/common_cgl.h" #include "gl/robwidget_gl.h" #include "gl/layout.h" #endif // TODO ideally use cached max of puglGetHWSurfaceScale #ifdef ROBTK_UPSCALE #define RTK_SCALE_MUL 2.0 #define RTK_SCALE_DIV 0.5 #else #define RTK_SCALE_MUL 1.0 #define RTK_SCALE_DIV 1.0 #endif #define C_RAD 5 #include "widgets/robtk_checkbutton.h" #include "widgets/robtk_checkimgbutton.h" #include "widgets/robtk_multibutton.h" #include "widgets/robtk_dial.h" #include "widgets/robtk_label.h" #include "widgets/robtk_pushbutton.h" #include "widgets/robtk_radiobutton.h" #include "widgets/robtk_scale.h" #include "widgets/robtk_separator.h" #include "widgets/robtk_spinner.h" #include "widgets/robtk_xyplot.h" #include "widgets/robtk_selector.h" #include "widgets/robtk_image.h" #include "widgets/robtk_drawingarea.h" #endif robtk-0.8.5/robtk.mk000066400000000000000000000107661463170413500143350ustar00rootroot00000000000000RT=$(RW)rtk/ WD=$(RW)widgets/robtk_ PKG_CONFIG?=pkg-config STRIP?=strip LIBSTRIPFLAGS?=-s APPSTRIPFLAGS?=-s WINDRES=$(XWIN)-windres UNAME?=$(shell uname) JACKEXTRA= OSXJACKWRAP= PTHREADCFLAGS= ifeq ($(UNAME),Darwin) OSXJACKWRAP=$(RW)jackwrap.mm USEWEAKJACK=1 LIBSTRIPFLAGS=-u -r -arch all -s $(RW)lv2uisyms APPSTRIPFLAGS=-u -r -arch all endif ifneq ($(XWIN),) USEWEAKJACK=1 JACKCFLAGS+=-mwindows ifeq ($(shell test -f img/x42.ico && echo yes), yes) JACKEXTRA+=win_icon.rc.o endif else PTHREADCFLAGS=-pthread endif ifeq ($(USEWEAKJACK),1) JACKCFLAGS+=-DUSE_WEAK_JACK JACKEXTRA+=$(RW)weakjack/weak_libjack.c ifeq ($(XWIN),) JACKLIBS+=-ldl endif else JACKLIBS+=`$(PKG_CONFIG) $(PKG_UI_FLAGS) --libs jack` endif ifeq ($(shell $(PKG_CONFIG) --exists liblo && echo yes), yes) JACKCFLAGS+=`$(PKG_CONFIG) $(PKG_UI_FLAGS) --cflags liblo` -DHAVE_LIBLO JACKLIBS+=`$(PKG_CONFIG) $(PKG_UI_FLAGS) --libs liblo` endif UITOOLKIT=$(WD)checkbutton.h $(WD)dial.h $(WD)label.h $(WD)pushbutton.h\ $(WD)radiobutton.h $(WD)scale.h $(WD)separator.h $(WD)spinner.h \ $(WD)xyplot.h $(WD)selector.h $(WD)multibutton.h \ $(WD)image.h $(WD)drawingarea.h $(WD)checkimgbutton.h ROBGL= $(RW)robtk.mk $(UITOOLKIT) $(RW)ui_gl.c $(PUGL_SRC) \ $(RW)gl/common_cgl.h $(RW)gl/layout.h $(RW)gl/robwidget_gl.h $(RW)robtk.h \ $(RT)common.h $(RT)style.h \ $(RW)gl/xternalui.c $(RW)gl/xternalui.h ROBGTK = $(RW)robtk.mk $(UITOOLKIT) $(RW)ui_gtk.c \ $(RW)gtk2/common_cgtk.h $(RW)gtk2/robwidget_gtk.h $(RW)robtk.h \ $(RT)common.h $(RT)style.h %UI_gtk.so %UI_gtk.dylib:: $(ROBGTK) @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(CFLAGS) $(GTKUICFLAGS) $(PTHREADCFLAGS) \ -DPLUGIN_SOURCE="\"gui/$(*F).c\"" \ -o $@ $(RW)ui_gtk.c \ $(value $(*F)_UISRC) \ -shared $(LV2LDFLAGS) $(LDFLAGS) $(GTKUILIBS) $(STRIP) ${LIBSTRIPFLAGS} $@ %UI_gl.o:: $(ROBGL) @mkdir -p $(@D) $(CXX) -c $(CPPFLAGS) $(CFLAGS) $(GLUICFLAGS) $(PTHREADCFLAGS) \ -DUINQHACK="$(shell date +%s$$$$)" \ -DPLUGIN_SOURCE="\"gui/$(*F).c\"" \ -DRTK_DESCRIPTOR="$(value gl_$(subst -,_,$(*F))_LV2DESC)" \ -o $@ $(RW)ui_gl.c %pugl.o:: $(ROBGL) @mkdir -p $(@D) $(CXX) -c $(CPPFLAGS) $(CFLAGS) $(GLUICFLAGS) \ -DUINQHACK="$(shell date +%s$$$$)" \ -o $@ $(PUGL_SRC) %_glui.so %_glui.dylib %_glui.dll:: @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(CFLAGS) $(GLUICFLAGS) $(PTHREADCFLAGS) \ -o $@ gui/$(*F).c \ $(GLGUIOBJ) \ $(value $(*F)_UISRC) \ -shared $(LV2LDFLAGS) $(LDFLAGS) $(GLUILIBS) $(STRIP) ${LIBSTRIPFLAGS} $@ %UI_gl.so %UI_gl.dylib %UI_gl.dll:: $(ROBGL) @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(CFLAGS) $(GLUICFLAGS) $(PTHREADCFLAGS) \ -DUINQHACK="$(shell date +%s$$$$)" \ -DPLUGIN_SOURCE="\"gui/$(*F).c\"" \ -o $@ $(RW)ui_gl.c \ $(PUGL_SRC) \ $(value $(*F)_UISRC) \ -shared $(LV2LDFLAGS) $(LDFLAGS) $(GLUILIBS) $(STRIP) ${LIBSTRIPFLAGS} $@ # ignore man-pages in rule below x42-%.1: @/bin/true # windows icon .SUFFFIXES: .rc win_icon.rc.o: $(RW)win_icon.rc img/x42.ico $(WINDRES) -o $@ $< x42-%.o:: $(ROBGL) @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(JACKCFLAGS) $(PTHREADCFLAGS) \ -DXTERNAL_UI -DHAVE_IDLE_IFACE -DDEFAULT_NOT_ONTOP \ -DRTK_DESCRIPTOR="$(value x42_$(subst -,_,$(*F))_JACKDESC)" \ -DPLUGIN_SOURCE="\"$(value x42_$(subst -,_,$(*F))_JACKGUI)\"" \ -o $@ \ -c $(RW)ui_gl.c x42-%-collection x42-%-collection.exe:: $(ROBGL) $(RW)jackwrap.c $(OSXJACKWRAP) $(RW)weakjack/weak_libjack.def $(RW)weakjack/weak_libjack.h $(JACKEXTRA) @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(JACKCFLAGS) $(PTHREADCFLAGS) -DDEFAULT_NOT_ONTOP \ -DXTERNAL_UI -DHAVE_IDLE_IFACE \ -DJACK_DESCRIPT="\"$(value x42_$(subst -,_,$(*F))_collection_LV2HTTL)\"" \ -DAPPNAME="\"$(*F)\"" \ -o $@ \ $(RW)jackwrap.c $(PUGL_SRC) $(OSXJACKWRAP) $(JACKEXTRA) \ $(value x42_$(subst -,_,$(*F))_collection_JACKSRC) \ $(LDFLAGS) $(JACKLIBS) $(STRIP) ${APPSTRIPFLAGS} $@ x42-% x42-%.exe:: $(ROBGL) $(RW)jackwrap.c $(OSXJACKWRAP) $(RW)weakjack/weak_libjack.def $(RW)weakjack/weak_libjack.h $(JACKEXTRA) @mkdir -p $(@D) $(CXX) $(CPPFLAGS) $(JACKCFLAGS) $(PTHREADCFLAGS) -DDEFAULT_NOT_ONTOP \ -DXTERNAL_UI -DHAVE_IDLE_IFACE \ -DRTK_DESCRIPTOR="$(value x42_$(subst -,_,$(*F))_JACKDESC)" \ -DPLUGIN_SOURCE="\"$(value x42_$(subst -,_,$(*F))_JACKGUI)\"" \ -DJACK_DESCRIPT="\"$(value x42_$(subst -,_,$(*F))_LV2HTTL)\"" \ -DAPPNAME="\"$(*F)\"" \ -o $@ \ $(RW)jackwrap.c $(RW)ui_gl.c $(PUGL_SRC) $(OSXJACKWRAP) $(JACKEXTRA) \ $(value x42_$(subst -,_,$(*F))_JACKSRC) \ $(LDFLAGS) $(JACKLIBS) $(STRIP) ${APPSTRIPFLAGS} $@ robtk-0.8.5/robtkapp.c000066400000000000000000000142001463170413500146340ustar00rootroot00000000000000/* robtk GUI application * * Copyright (C) 2012-2015 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2 of the License, or * (at your option) any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program. If not, see . */ #ifndef UI_UPDATE_FPS #define UI_UPDATE_FPS 25 #endif /////////////////////////////////////////////////////////////////////////////// #ifndef _GNU_SOURCE #define _GNU_SOURCE #endif #ifdef WIN32 #include #include #define pthread_t //< override jack.h def #endif #ifdef HAVE_LV2_1_18_6 #include #include #else #include #include #endif #ifdef __APPLE__ #include extern void rtk_osx_api_init(void); extern void rtk_osx_api_terminate(void); extern void rtk_osx_api_run(void); extern void rtk_osx_api_err(const char *msg); #endif #include #include #include #include #include #include #include #include #include #if (defined _WIN32 && defined RTK_STATIC_INIT) #include #endif #ifndef _WIN32 #include #include #else #include #endif #include "./gl/xternalui.h" #define LV2_EXTERNAL_UI_RUN(ptr) (ptr)->run(ptr) #define LV2_EXTERNAL_UI_SHOW(ptr) (ptr)->show(ptr) #define LV2_EXTERNAL_UI_HIDE(ptr) (ptr)->hide(ptr) #define nan NAN typedef struct _RtkLv2Description { const LV2UI_Descriptor* (*lv2ui_descriptor)(uint32_t index); const uint32_t gui_descriptor_id; const char *plugin_human_id; } RtkLv2Description; static const LV2UI_Descriptor *plugin_gui; //static LV2_Handle plugin_instance = NULL; static LV2UI_Handle gui_instance = NULL; static pthread_mutex_t gui_thread_lock = PTHREAD_MUTEX_INITIALIZER; static pthread_cond_t data_ready = PTHREAD_COND_INITIALIZER; static RtkLv2Description const *inst; /* a simple state machine for this client */ static volatile enum { Run, Exit } client_state = Run; struct lv2_external_ui_host extui_host; struct lv2_external_ui *extui = NULL; static const RtkLv2Description _plugin = { & RTK_DESCRIPTOR, 0, APPTITLE }; /****************************************************************************** * MAIN */ static void cleanup(int sig) { if (plugin_gui && gui_instance && plugin_gui->cleanup) { plugin_gui->cleanup(gui_instance); } fprintf(stderr, "bye.\n"); } static void run_one() { plugin_gui->port_event(gui_instance, 0, 0, 0, NULL); LV2_EXTERNAL_UI_RUN(extui); } #ifdef __APPLE__ static void osx_loop (CFRunLoopTimerRef timer, void *info) { if (client_state == Run) { run_one(); } if (client_state == Exit) { rtk_osx_api_terminate(); } } #else static void main_loop(void) { struct timespec timeout; pthread_mutex_lock (&gui_thread_lock); while (client_state != Exit) { run_one(); if (client_state == Exit) break; #ifdef _WIN32 //Sleep(1000/UI_UPDATE_FPS); #if (defined(__MINGW64__) || defined(__MINGW32__)) && __MSVCRT_VERSION__ >= 0x0601 struct __timeb64 timebuffer; _ftime64(&timebuffer); #else struct __timeb32 timebuffer; _ftime(&timebuffer); #endif timeout.tv_nsec = timebuffer.millitm * 1000000; timeout.tv_sec = timebuffer.time; #else // POSIX clock_gettime(CLOCK_REALTIME, &timeout); #endif timeout.tv_nsec += 1000000000 / (UI_UPDATE_FPS); if (timeout.tv_nsec >= 1000000000) {timeout.tv_nsec -= 1000000000; timeout.tv_sec+=1;} pthread_cond_timedwait (&data_ready, &gui_thread_lock, &timeout); } /* while running */ pthread_mutex_unlock (&gui_thread_lock); } #endif // APPLE RUNLOOP static void catchsig (int sig) { fprintf(stderr,"caught signal - shutting down.\n"); client_state=Exit; pthread_cond_signal (&data_ready); } static void on_external_ui_closed(void* controller) { catchsig(0); } int main (int argc, char **argv) { int rv = 0; inst = & _plugin; #ifdef __APPLE__ rtk_osx_api_init(); #endif #ifdef _WIN32 pthread_win32_process_attach_np(); #endif #if (defined _WIN32 && defined RTK_STATIC_INIT) glib_init_static(); gobject_init_ctor(); #endif struct { int argc; char **argv; } rtkargv; rtkargv.argc = argc; rtkargv.argv = argv; const LV2_Feature external_lv_feature = { LV2_EXTERNAL_UI_URI, &extui_host}; const LV2_Feature external_kx_feature = { LV2_EXTERNAL_UI_URI__KX__Host, &extui_host}; const LV2_Feature robtk_argv = { "http://gareus.org/oss/lv2/robtk#argv", &rtkargv}; // TODO add argv[] as feature const LV2_Feature* ui_features[] = { &external_lv_feature, &external_kx_feature, &robtk_argv, NULL }; extui_host.plugin_human_id = inst->plugin_human_id; plugin_gui = inst->lv2ui_descriptor(inst->gui_descriptor_id); if (plugin_gui) { /* init plugin GUI */ extui_host.ui_closed = on_external_ui_closed; gui_instance = plugin_gui->instantiate(plugin_gui, "URI TODO", NULL, NULL, NULL, (void **)&extui, ui_features); } if (!gui_instance || !extui) { fprintf(stderr, "Error: GUI was not initialized.\n"); rv |= 2; goto out; } #ifndef _WIN32 signal (SIGHUP, catchsig); signal (SIGINT, catchsig); #endif { LV2_EXTERNAL_UI_SHOW(extui); #ifdef __APPLE__ CFRunLoopRef runLoop = CFRunLoopGetCurrent(); CFRunLoopTimerContext context = {0, NULL, NULL, NULL, NULL}; CFRunLoopTimerRef timer = CFRunLoopTimerCreate(kCFAllocatorDefault, 0, 1.0/UI_UPDATE_FPS, 0, 0, &osx_loop, &context); CFRunLoopAddTimer(runLoop, timer, kCFRunLoopCommonModes); rtk_osx_api_run(); #else main_loop(); #endif LV2_EXTERNAL_UI_HIDE(extui); } out: cleanup(0); #ifdef _WIN32 pthread_win32_process_detach_np(); #endif #if (defined _WIN32 && defined RTK_STATIC_INIT) glib_cleanup_static(); #endif return(rv); } /* vi:set ts=2 sts=2 sw=2: */ robtk-0.8.5/rtk/000077500000000000000000000000001463170413500134515ustar00rootroot00000000000000robtk-0.8.5/rtk/common.h000066400000000000000000000253731463170413500151240ustar00rootroot00000000000000/* * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef RTK_CAIRO_H #define RTK_CAIRO_H #include #include #include #include static float rtk_hue2rgb(const float p, const float q, float t) { if(t < 0.f) t += 1.f; if(t > 1.f) t -= 1.f; if(t < 1.f/6.f) return p + (q - p) * 6.f * t; if(t < 1.f/2.f) return q; if(t < 2.f/3.f) return p + (q - p) * (2.f/3.f - t) * 6.f; return p; } static void rgba_to_hsva (float* hsva, float const* rgba) { float r = rgba[0]; float g = rgba[1]; float b = rgba[2]; hsva[3] = rgba[3]; float cmax = fmaxf (r, fmaxf (g, b)); float cmin = fminf (r, fminf (g, b)); hsva[2] = cmax; // v if (cmax == 0) { // r = g = b == 0 ... v is undefined, s = 0 hsva[0] = 0; // h hsva[1] = 0; // s return; } float delta = cmax - cmin; if (delta != 0.0) { if (cmax == r) { hsva[0] = fmodf ((g - b) / delta, 6.0); } else if (cmax == g) { hsva[0] = ((b - r) / delta) + 2; } else { hsva[0] = ((r - g) / delta) + 4; } hsva[0] *= 60.0; if (hsva[0] < 0.0) { /* negative values are legal but confusing, because they alias positive values. */ hsva[0] = 360 + hsva[0]; } } else { hsva[0] = 0; // h hsva[1] = 0; // s return; } if (delta == 0 || cmax == 0) { hsva[1] = 0; } else { hsva[1] = delta / cmax; } } static void hsva_to_rgba (float* rgba, float* hsva) { float h = hsva[0]; float s = hsva[1]; float v = hsva[2]; rgba[3] = hsva[3]; s = fminf (1.f, fmaxf (0.f, s)); v = fminf (1.f, fmaxf (0.f, v)); h = fmodf (h + 360.f, 360.f); if (s == 0) { rgba[0] = rgba[1] = rgba[2] = v; return; } float c = v * s; float x = c * (1.f - fabsf (fmodf (h / 60.f, 2) - 1.f)); float m = v - c; #define SETRGBA(R, G, B) rgba[0] = R; rgba[1] = G; rgba[2] = B; if (h >= 0.0 && h < 60.0) { SETRGBA (c + m, x + m, m); } else if (h >= 60.0 && h < 120.0) { SETRGBA (x + m, c + m, m); } else if (h >= 120.0 && h < 180.0) { SETRGBA (m, c + m, x + m); } else if (h >= 180.0 && h < 240.0) { SETRGBA (m, x + m, c + m); } else if (h >= 240.0 && h < 300.0) { SETRGBA (x + m, m, c + m); } else if (h >= 300.0 && h < 360.0) { SETRGBA (c + m, m, x + m); } else { SETRGBA (m, m, m); } #undef SETRGBA } static void interpolate_hue (float* c, const float* c1, const float* c2, float f) { assert (f >= 0.f && f <= 1.f); float h1[4]; float h2[4]; rgba_to_hsva (h1, c1); rgba_to_hsva (h2, c2); float d = h2[0] - h1[0]; if (d > 180) { d -= 360; } else if (d < -180) { d += 360; } assert (fabsf(d) <= 180); h1[0] = fmodf (360 + h1[0] + f * d, 360); h1[1] += f * (h2[1] - h1[1]); h1[2] += f * (h2[2] - h1[2]); h1[3] += f * (h2[3] - h1[3]); hsva_to_rgba (c, h1); } static void interpolate_rgb (float* c, const float* c1, const float* c2, float f) { assert (f >= 0.f && f <= 1.f); c[0] = c1[0] + f * (c2[0] - c1[0]); c[1] = c1[1] + f * (c2[1] - c1[1]); c[2] = c1[2] + f * (c2[2] - c1[2]); c[3] = fmax (c1[3], c2[3]); } static void interpolate_fg_bg (float* c, float fract) { float c_bg[4]; float c_fg[4]; get_color_from_theme (0, c_fg); get_color_from_theme (1, c_bg); interpolate_hue (c, c_fg, c_bg, fract); } static uint32_t rgba_to_hex (float *c) { float r = fminf (1.f, fmaxf (0.f, c[0])); float g = fminf (1.f, fmaxf (0.f, c[1])); float b = fminf (1.f, fmaxf (0.f, c[2])); float a = fminf (1.f, fmaxf (0.f, c[3])); uint32_t rc, gc, bc, ac; rc = rint (r * 255.0); gc = rint (g * 255.0); bc = rint (b * 255.0); ac = rint (a * 255.0); return (rc << 24) | (gc << 16) | (bc << 8) | ac; } static float inv_gamma_srgb (const float v) { if (v <= 0.04045) { return v / 12.92; } else { return pow(((v + 0.055) / (1.055)), 2.4); } } static float gamma_srgb (float v) { if (v <= 0.0031308) { v *= 12.92; } else { v = 1.055 * powf (v, 1.0 / 2.4) - 0.055; } return v; } static float luminance_rgb (float const* c) { const float rY = 0.212655; const float gY = 0.715158; const float bY = 0.072187; return gamma_srgb (rY * inv_gamma_srgb (c[0]) + gY * inv_gamma_srgb(c[1]) + bY * inv_gamma_srgb (c[2])); } static void rounded_rectangle (cairo_t* cr, double x, double y, double w, double h, double r) { double degrees = M_PI / 180.0; cairo_new_sub_path (cr); cairo_arc (cr, x + w - r, y + r, r, -90 * degrees, 0 * degrees); cairo_arc (cr, x + w - r, y + h - r, r, 0 * degrees, 90 * degrees); cairo_arc (cr, x + r, y + h - r, r, 90 * degrees, 180 * degrees); cairo_arc (cr, x + r, y + r, r, 180 * degrees, 270 * degrees); cairo_close_path (cr); } static void get_text_geometry( const char *txt, PangoFontDescription *font, int *tw, int *th) { cairo_surface_t* tmp = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, 8, 8); cairo_t *cr = cairo_create (tmp); PangoLayout * pl = pango_cairo_create_layout(cr); pango_layout_set_font_description(pl, font); if (strncmp(txt, "", 8)) { pango_layout_set_text(pl, txt, -1); } else { pango_layout_set_markup(pl, txt, -1); } pango_layout_get_pixel_size(pl, tw, th); g_object_unref(pl); cairo_destroy (cr); cairo_surface_destroy(tmp); } static void write_text_full( cairo_t* cr, const char *txt, PangoFontDescription *font, const float x, const float y, const float ang, const int align, const float * const col) { int tw, th; cairo_save(cr); PangoLayout * pl = pango_cairo_create_layout(cr); pango_layout_set_font_description(pl, font); if (strncmp(txt, "", 8)) { pango_layout_set_text(pl, txt, -1); } else { pango_layout_set_markup(pl, txt, -1); } pango_layout_get_pixel_size(pl, &tw, &th); cairo_translate (cr, rintf(x), rintf(y)); if (ang != 0) { cairo_rotate (cr, ang); } switch(abs(align)) { case 1: cairo_translate (cr, -tw, ceil(th/-2.0)); pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT); break; case 2: cairo_translate (cr, ceil(tw/-2.0), ceil(th/-2.0)); pango_layout_set_alignment (pl, PANGO_ALIGN_CENTER); break; case 3: cairo_translate (cr, 0, ceil(th/-2.0)); pango_layout_set_alignment (pl, PANGO_ALIGN_LEFT); break; case 4: cairo_translate (cr, -tw, -th); pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT); break; case 5: cairo_translate (cr, ceil(tw/-2.0), -th); pango_layout_set_alignment (pl, PANGO_ALIGN_CENTER); break; case 6: cairo_translate (cr, 0, -th); pango_layout_set_alignment (pl, PANGO_ALIGN_LEFT); break; case 7: cairo_translate (cr, -tw, 0); pango_layout_set_alignment (pl, PANGO_ALIGN_RIGHT); break; case 8: cairo_translate (cr, ceil(tw/-2.0), 0); pango_layout_set_alignment (pl, PANGO_ALIGN_CENTER); break; case 9: cairo_translate (cr, 0, 0); pango_layout_set_alignment (pl, PANGO_ALIGN_LEFT); break; default: break; } if (align < 0) { cairo_set_source_rgba (cr, .0, .0, .0, .5); cairo_rectangle (cr, 0, 0, tw, th); cairo_fill (cr); } #if 1 cairo_set_source_rgba (cr, col[0], col[1], col[2], col[3]); pango_cairo_show_layout(cr, pl); #else cairo_set_source_rgba (cr, col[0], col[1], col[2], col[3]); pango_cairo_layout_path(cr, pl); cairo_fill(cr); #endif g_object_unref(pl); cairo_restore(cr); cairo_new_path (cr); } static void create_text_surface3s(cairo_surface_t ** sf, const float w, const float h, float x, float y, const char * txt, PangoFontDescription *font, const float * const c_col, const float scale) { assert(sf); if (*sf) { cairo_surface_destroy(*sf); } *sf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ceilf(w * scale), ceilf(h * scale)); cairo_t *cr = cairo_create (*sf); cairo_set_source_rgba (cr, .0, .0, .0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle (cr, 0, 0, ceil(w * scale), ceil(h * scale)); cairo_fill (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_scale (cr, scale, scale); x = floor (scale * x) + 1; y = floor (scale * y) + 1; write_text_full(cr, txt, font, ceil(x / scale), ceil(y / scale), 0, 2, c_col); cairo_surface_flush(*sf); cairo_destroy (cr); } static void create_text_surface3(cairo_surface_t ** sf, const float w, const float h, const float x, const float y, const char * txt, PangoFontDescription *font, const float * const c_col, const float scale) { assert(sf); if (*sf) { cairo_surface_destroy(*sf); } *sf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ceilf(w), ceilf(h)); cairo_t *cr = cairo_create (*sf); cairo_set_source_rgba (cr, .0, .0, .0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle (cr, 0, 0, ceil(w), ceil(h)); cairo_fill (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_scale (cr, scale, scale); write_text_full(cr, txt, font, ceil(x / scale), ceil(y / scale), 0, 2, c_col); cairo_surface_flush(*sf); cairo_destroy (cr); } static void create_text_surface(cairo_surface_t ** sf, const float w, const float h, const float x, const float y, const char * txt, PangoFontDescription *font, const float * const c_col) { return create_text_surface3 (sf, w, h, x, y, txt, font, c_col, 1.0); } static void create_text_surface2(cairo_surface_t ** sf, const float w, const float h, const float x, const float y, const char * txt, PangoFontDescription *font, float ang, int align, const float * const c_col) { assert(sf); if (*sf) { cairo_surface_destroy(*sf); } *sf = cairo_image_surface_create(CAIRO_FORMAT_ARGB32, ceilf(w), ceilf(h)); cairo_t *cr = cairo_create (*sf); cairo_set_source_rgba (cr, .0, .0, .0, 0); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle (cr, 0, 0, ceil(w), ceil(h)); cairo_fill (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); write_text_full(cr, txt, font, ceil(x), ceil(y), ang, align, c_col); cairo_surface_flush(*sf); cairo_destroy (cr); } #ifdef _WIN32 # include # include #elif defined __APPLE__ // defined in pugl/pugl_osx.mm extern bool rtk_osx_open_url (const char* url); #else # include #endif static void rtk_open_url (const char *url) { // assume URL is escaped and shorter than 1024 chars; #ifdef _WIN32 ShellExecute(NULL, "open", url, NULL, NULL, SW_SHOWNORMAL); #elif defined __APPLE__ rtk_osx_open_url (url); #else char tmp[1024]; sprintf(tmp, "xdg-open %s &", url); (void) system (tmp); #endif } #endif robtk-0.8.5/rtk/style.h000066400000000000000000000064711463170413500147720ustar00rootroot00000000000000#ifndef RTK_STYLE_H #define RTK_STYLE_H /* colors */ static const float c_trs[4] = {0.0, 0.0, 0.0, 0.0}; static const float c_blk[4] = {0.0, 0.0, 0.0, 1.0}; static const float c_wht[4] = {1.0, 1.0, 1.0, 1.0}; static const float c_g90[4] = {0.9, 0.9, 0.9, 1.0}; static const float c_g80[4] = {0.8, 0.8, 0.8, 1.0}; // dbm gain lines static const float c_g70[4] = {0.7, 0.7, 0.7, 1.0}; static const float c_g60[4] = {0.6, 0.6, 0.6, 1.0}; // dpm border static const float c_gry[4] = {0.5, 0.5, 0.5, 1.0}; static const float c_g40[4] = {0.4, 0.4, 0.4, 1.0}; static const float c_g30[4] = {0.3, 0.3, 0.3, 1.0}; static const float c_g20[4] = {0.2, 0.2, 0.2, 1.0}; static const float c_g10[4] = {0.1, 0.1, 0.1, 1.0}; static const float c_g05[4] = {0.05, 0.05, 0.05, 1.0}; static const float c_red[4] = {1.0, 0.0, 0.0, 1.0}; static const float c_rd2[4] = {0.25,0.0, 0.0, 1.0}; // hist-off static const float c_nrd[4] = {0.9, 0.2, 0.2, 1.0}; // nordicred static const float c_nvu[4] = {0.9, 0.1, 0.1, 1.0}; // VU red static const float c_prd[4] = {0.8, 0.2, 0.2, 1.0}; // peak red static const float c_ptr[4] = {0.6, 0.0, 0.0, 1.0}; // dpm numerical peak bg static const float c_grn[4] = {0.0, 1.0, 0.0, 1.0}; static const float c_lgg[4] = {0.9, 0.95,0.9, 1.0}; static const float c_ngr[4] = {0.2, 0.9, 0.2, 1.0}; // phasedgreen static const float c_nyl[4] = {0.9, 0.9, 0.0, 1.0}; // meanyellow static const float c_ora[4] = {0.8, 0.5, 0.0, 1.0}; static const float c_grb[4] = {0.5, 0.5, 0.6, 1.0}; // goniometer ann static const float c_glr[4] = {0.7, 0.7, 0.8, 1.0}; // phasemtrann static const float c_glb[4] = {0.7, 0.7, 0.1, 1.0}; // phasemtr static const float c_g7X[4] = {0.7, 0.7, 0.7, 0.3}; // radar line static const float c_an0[4] = {0.6, 0.6, 0.9, 0.75}; // radar annotation static const float c_an1[4] = {0.5, 0.5, 0.8, 0.75}; // radar annotation static const float c_gml[4] = {0.88, 0.88, 0.15, 0.6}; // goni lines static const float c_gmp[4] = {0.88, 0.88, 0.15, 0.7}; // goni points static const float c_hlt[4] = {1.0, 1.0, 1.0, 0.3}; // dpm highlight static const float c_xfb[4] = {0.0, 0.0, 0.0, 0.8}; // dpm ann bg static const float c_scr[4] = {0.2, 0.2, 0.2, 0.8}; // screw mount #ifdef RTK_INLINE_STYLE static void get_color_from_theme (int which, float *col) { switch(which) { default: // fg col[0] = col[1] = col[2] = .9; col[3] = 1.0; break; case 1: // bg col[0] = col[1] = col[2] = 61/255.0; col[3] = 1.0; break; } } #endif #define CairoSetSouerceRGBA(COL) \ cairo_set_source_rgba (cr, (COL)[0], (COL)[1], (COL)[2], (COL)[3]) #define CairoSetSouerceRGBADarkLight(COLD, COLL) \ do { \ if (is_light_theme()) { \ cairo_set_source_rgba (cr, (COLL)[0], (COLL)[1], (COLL)[2], (COLL)[3]); \ } else { \ cairo_set_source_rgba (cr, (COLD)[0], (COLD)[1], (COLD)[2], (COLD)[3]); \ } \ } while (0) #define ISBRIGHT(COL) (luminance_rgb (COL) >= 0.5) #define SHADE_RGB(COL, X) \ ISBRIGHT(COL) ? COL[0] / (X) : COL[0] * (X), \ ISBRIGHT(COL) ? COL[1] / (X) : COL[1] * (X), \ ISBRIGHT(COL) ? COL[2] / (X) : COL[2] * (X) #endif robtk-0.8.5/sofd/000077500000000000000000000000001463170413500136045ustar00rootroot00000000000000robtk-0.8.5/sofd/libsofd.c000066400000000000000000002020071463170413500153730ustar00rootroot00000000000000/* libSOFD - Simple Open File Dialog [for X11 without toolkit] * * Copyright (C) 2014 Robin Gareus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ /* Test and example: * gcc -Wall -D SOFD_TEST -g -o sofd libsofd.c -lX11 * * public API documentation and example code at the bottom of this file * * This small lib may one day include openGL rendering and * wayland window support, but not today. Today we celebrate * 30 years of X11. */ #ifdef SOFD_TEST #define HAVE_X11 #include "libsofd.h" #endif #include #include #include #include #include #include #include #include #include #include // shared 'recently used' implementation // sadly, xbel does not qualify as simple. // hence we use a simple format alike the // gtk-bookmark list (one file per line) #define MAX_RECENT_ENTRIES 24 #define MAX_RECENT_AGE (15552000) // 180 days (in sec) typedef struct { char path[1024]; time_t atime; } FibRecentFile; static FibRecentFile *_recentlist = NULL; static unsigned int _recentcnt = 0; static uint8_t _recentlock = 0; static int fib_isxdigit (const char x) { if ( (x >= '0' && x <= '9') || (x >= 'a' && x <= 'f') || (x >= 'A' && x <= 'F') ) return 1; return 0; } static void decode_3986 (char *str) { int len = strlen (str); int idx = 0; while (idx + 2 < len) { char *in = &str[idx]; if (('%' == *in) && fib_isxdigit (in[1]) && fib_isxdigit (in[2])) { char hexstr[3]; hexstr[0] = in[1]; hexstr[1] = in[2]; hexstr[2] = 0; long hex = strtol (hexstr, NULL, 16); *in = hex; memmove (&str[idx+1], &str[idx + 3], len - idx - 2); len -= 2; } ++idx; } } static char *encode_3986 (const char *str) { size_t alloc, newlen; char *ns = NULL; unsigned char in; size_t i = 0; size_t length; if (!str) return strdup (""); alloc = strlen (str) + 1; newlen = alloc; ns = (char*) malloc (alloc); length = alloc; while (--length) { in = *str; switch (in) { case '0': case '1': case '2': case '3': case '4': case '5': case '6': case '7': case '8': case '9': case 'a': case 'b': case 'c': case 'd': case 'e': case 'f': case 'g': case 'h': case 'i': case 'j': case 'k': case 'l': case 'm': case 'n': case 'o': case 'p': case 'q': case 'r': case 's': case 't': case 'u': case 'v': case 'w': case 'x': case 'y': case 'z': case 'A': case 'B': case 'C': case 'D': case 'E': case 'F': case 'G': case 'H': case 'I': case 'J': case 'K': case 'L': case 'M': case 'N': case 'O': case 'P': case 'Q': case 'R': case 'S': case 'T': case 'U': case 'V': case 'W': case 'X': case 'Y': case 'Z': case '_': case '~': case '.': case '-': case '/': case ',': // XXX not in RFC3986 ns[i++] = in; break; default: newlen += 2; /* this'll become a %XX */ if (newlen > alloc) { alloc *= 2; ns = (char*) realloc (ns, alloc); } snprintf (&ns[i], 4, "%%%02X", in); i += 3; break; } ++str; } ns[i] = 0; return ns; } void x_fib_free_recent () { free (_recentlist); _recentlist = NULL; _recentcnt = 0; } static int cmp_recent (const void *p1, const void *p2) { FibRecentFile *a = (FibRecentFile*) p1; FibRecentFile *b = (FibRecentFile*) p2; if (a->atime == b->atime) return 0; return a->atime < b->atime; } int x_fib_add_recent (const char *path, time_t atime) { unsigned int i; struct stat fs; if (_recentlock) { return -1; } if (access (path, R_OK)) { return -1; } if (stat (path, &fs)) { return -1; } if (!S_ISREG (fs.st_mode)) { return -1; } if (atime == 0) atime = time (NULL); if (MAX_RECENT_AGE > 0 && atime + MAX_RECENT_AGE < time (NULL)) { return -1; } for (i = 0; i < _recentcnt; ++i) { if (!strcmp (_recentlist[i].path, path)) { if (_recentlist[i].atime < atime) { _recentlist[i].atime = atime; } qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); return _recentcnt; } } _recentlist = (FibRecentFile*)realloc (_recentlist, (_recentcnt + 1) * sizeof(FibRecentFile)); _recentlist[_recentcnt].atime = atime; strcpy (_recentlist[_recentcnt].path, path); qsort (_recentlist, _recentcnt + 1, sizeof(FibRecentFile), cmp_recent); if (_recentcnt >= MAX_RECENT_ENTRIES) { return (_recentcnt); } return (++_recentcnt); } #ifdef PATHSEP #undef PATHSEP #endif #ifdef PLATFORM_WINDOWS #define DIRSEP '\\' #else #define DIRSEP '/' #endif static void mkpath(const char *dir) { char tmp[1024]; char *p; size_t len; snprintf (tmp, sizeof(tmp), "%s", dir); len = strlen(tmp); if (tmp[len - 1] == '/') tmp[len - 1] = 0; for (p = tmp + 1; *p; ++p) if(*p == DIRSEP) { *p = 0; #ifdef PLATFORM_WINDOWS mkdir(tmp); #else mkdir(tmp, 0755); #endif *p = DIRSEP; } #ifdef PLATFORM_WINDOWS mkdir(tmp); #else mkdir(tmp, 0755); #endif } int x_fib_save_recent (const char *fn) { if (_recentlock) { return -1; } if (!fn) { return -1; } if (_recentcnt < 1 || !_recentlist) { return -1; } unsigned int i; char *dn = strdup (fn); mkpath (dirname (dn)); free (dn); FILE *rf = fopen (fn, "w"); if (!rf) return -1; qsort (_recentlist, _recentcnt, sizeof(FibRecentFile), cmp_recent); for (i = 0; i < _recentcnt; ++i) { char *n = encode_3986 (_recentlist[i].path); fprintf (rf, "%s %lu\n", n, _recentlist[i].atime); free (n); } fclose (rf); return 0; } int x_fib_load_recent (const char *fn) { char tmp[1024]; if (_recentlock) { return -1; } if (!fn) { return -1; } x_fib_free_recent (); if (access (fn, R_OK)) { return -1; } FILE *rf = fopen (fn, "r"); if (!rf) return -1; while (fgets (tmp, sizeof(tmp), rf) && strlen (tmp) > 1 && strlen (tmp) < sizeof(tmp)) { char *s; tmp[strlen (tmp) - 1] = '\0'; // strip newline if (!(s = strchr (tmp, ' '))) { // find name <> atime sep continue; } *s = '\0'; time_t t = atol (++s); decode_3986 (tmp); x_fib_add_recent (tmp, t); } fclose (rf); return 0; } unsigned int x_fib_recent_count () { return _recentcnt; } const char *x_fib_recent_at (unsigned int i) { if (i >= _recentcnt) return NULL; return _recentlist[i].path; } #ifdef PLATFORM_WINDOWS #define PATHSEP "\\" #else #define PATHSEP "/" #endif const char *x_fib_recent_file(const char *appname) { static char recent_file[1024]; assert(!strchr(appname, '/')); const char *xdg = getenv("XDG_DATA_HOME"); if (xdg && (strlen(xdg) + strlen(appname) + 10) < sizeof(recent_file)) { sprintf(recent_file, "%s" PATHSEP "%s" PATHSEP "recent", xdg, appname); return recent_file; } #ifdef PLATFORM_WINDOWS const char * homedrive = getenv("HOMEDRIVE"); const char * homepath = getenv("HOMEPATH"); if (homedrive && homepath && (strlen(homedrive) + strlen(homepath) + strlen(appname) + 29) < PATH_MAX) { sprintf(recent_file, "%s%s" PATHSEP "Application Data" PATHSEP "%s" PATHSEP "recent.txt", homedrive, homepath, appname); return recent_file; } #elif defined PLATFORM_OSX const char *home = getenv("HOME"); if (home && (strlen(home) + strlen(appname) + 29) < sizeof(recent_file)) { sprintf(recent_file, "%s/Library/Preferences/%s/recent", home, appname); return recent_file; } #else const char *home = getenv("HOME"); if (home && (strlen(home) + strlen(appname) + 22) < sizeof(recent_file)) { sprintf(recent_file, "%s/.local/share/%s/recent", home, appname); return recent_file; } #endif return NULL; } #ifdef HAVE_X11 #include #include #include #include #include #include #include #ifndef MIN #define MIN(A,B) ( (A) < (B) ? (A) : (B) ) #endif #ifndef MAX #define MAX(A,B) ( (A) < (B) ? (B) : (A) ) #endif static Window _fib_win = 0; static GC _fib_gc = 0; static XColor _c_gray0, _c_gray1, _c_gray2, _c_gray3, _c_gray4, _c_gray5, _c_gray6; static Font _fibfont = 0; static Pixmap _pixbuffer = None; static int _fib_width = 100; static int _fib_height = 100; static int _btn_w = 0; static int _btn_span = 0; static int _fib_font_height = 0; static int _fib_dir_indent = 0; static int _fib_spc_norm = 0; static int _fib_font_ascent = 0; static int _fib_font_vsep = 0; static int _fib_font_size_width = 0; static int _fib_font_time_width = 0; static int _fib_place_width = 0; static int _scrl_f = 0; static int _scrl_y0 = -1; static int _scrl_y1 = -1; static int _scrl_my = -1; static int _scrl_mf = -1; static int _view_p = -1; static int _fsel = -1; static int _hov_b = -1; static int _hov_f = -1; static int _hov_p = -1; static int _hov_h = -1; static int _hov_l = -1; static int _hov_s = -1; static int _sort = 0; static int _columns = 0; static int _fib_filter_fn = 1; static int _fib_hidden_fn = 0; static int _fib_show_places = 0; static uint8_t _fib_mapped = 0; static uint8_t _fib_resized = 0; static unsigned long _dblclk = 0; static int _status = -2; static char _rv_open[1024] = ""; static char _fib_cfg_custom_places[1024] = ""; static char _fib_cfg_custom_font[256] = ""; static char _fib_cfg_title[128] = "xjadeo - Open Video File"; typedef struct { char name[256]; int x0; int xw; } FibPathButton; typedef struct { char name[256]; char strtime[32]; char strsize[32]; int ssizew; off_t size; time_t mtime; uint8_t flags; // 2: selected, 4: isdir 8: recent-entry FibRecentFile *rfp; } FibFileEntry; typedef struct { char text[24]; uint8_t flags; // 2: selected, 4: toggle, 8 disable int x0; int tw; int xw; void (*callback)(Display*); } FibButton; typedef struct { char name[256]; char path[1024]; uint8_t flags; // 1: hover, 2: selected, 4:add sep } FibPlace; static char _cur_path[1024] = ""; static FibFileEntry *_dirlist = NULL; static FibPathButton *_pathbtn = NULL; static FibPlace *_placelist = NULL; static int _dircount = 0; static int _pathparts = 0; static int _placecnt = 0; static FibButton _btn_ok; static FibButton _btn_cancel; static FibButton _btn_filter; static FibButton _btn_places; static FibButton _btn_hidden; static FibButton *_btns[] = {&_btn_places, &_btn_filter, &_btn_hidden, &_btn_cancel, &_btn_ok}; static int (*_fib_filter_function)(const char *filename); /* hardcoded layout */ #define DSEP 6 // px; horiz space beween elements, also l+r margin for file-list #define PSEP 4 // px; horiz space beween paths #define FILECOLUMN (17 * _fib_dir_indent) //px; min width of file-column #define LISTTOP 2.7 //em; top of the file-browser list #define LISTBOT 4.75 //em; bottom of the file-browers list #define BTNBTMMARGIN 0.75 //em; height/margin of the button row #define BTNPADDING 2 // px - only used for open/cancel buttons #define SCROLLBARW (3 + (_fib_spc_norm&~1)) //px; - should be SCROLLBARW = (N * 2 + 3) #define SCROLLBOXH 10 //px; arrow box top+bottom #define PLACESW _fib_place_width //px; #define PLACESWMAX (15 *_fib_spc_norm) //px; #define PATHBTNTOP _fib_font_vsep //px; offset by (_fib_font_ascent); #define FAREAMRGB 3 //px; base L+R margin #define FAREAMRGR (FAREAMRGB + 1) //px; right margin of file-area + 1 (line width) #define FAREAMRGL (_fib_show_places ? PLACESW + FAREAMRGB : FAREAMRGB) //px; left margin of file-area #define TEXTSEP 4 //px; #define FAREATEXTL (FAREAMRGL + TEXTSEP) //px; filename text-left FAREAMRGL + TEXTSEP #define SORTBTNOFF -10 //px; #ifndef DBLCLKTME #define DBLCLKTME 200 //msec; double click time #endif #define DRAW_OUTLINE #define DOUBLE_BUFFER static int query_font_geometry (Display *dpy, GC gc, const char *txt, int *w, int *h, int *a, int *d) { XCharStruct text_structure; int font_direction, font_ascent, font_descent; XFontStruct *fontinfo = XQueryFont (dpy, XGContextFromGC (gc)); if (!fontinfo) { return -1; } XTextExtents (fontinfo, txt, strlen (txt), &font_direction, &font_ascent, &font_descent, &text_structure); if (w) *w = XTextWidth (fontinfo, txt, strlen (txt)); if (h) *h = text_structure.ascent + text_structure.descent; if (a) *a = text_structure.ascent; if (d) *d = text_structure.descent; XFreeFontInfo (NULL, fontinfo, 1); return 0; } static void VDrawRectangle (Display *dpy, Drawable d, GC gc, int x, int y, unsigned int w, unsigned int h) { const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); #ifdef DRAW_OUTLINE XSetForeground (dpy, gc, _c_gray5.pixel); XDrawLine (dpy, d, gc, x + 1, y + h, x + w, y + h); XDrawLine (dpy, d, gc, x + w, y + 1, x + w, y + h); XSetForeground (dpy, gc, blackColor); XDrawLine (dpy, d, gc, x + 1, y, x + w, y); XDrawLine (dpy, d, gc, x, y + 1, x, y + h); #else XSetForeground (dpy, _fib_gc, blackColor); XDrawRectangle (dpy, d, gc, x, y, w, h); #endif } static void fib_expose (Display *dpy, Window realwin) { int i; XID win; const unsigned long whiteColor = WhitePixel (dpy, DefaultScreen (dpy)); const unsigned long blackColor = BlackPixel (dpy, DefaultScreen (dpy)); if (!_fib_mapped) return; if (_fib_resized #ifdef DOUBLE_BUFFER || !_pixbuffer #endif ) { #ifdef DOUBLE_BUFFER unsigned int w = 0, h = 0; if (_pixbuffer != None) { Window ignored_w; int ignored_i; unsigned int ignored_u; XGetGeometry(dpy, _pixbuffer, &ignored_w, &ignored_i, &ignored_i, &w, &h, &ignored_u, &ignored_u); if (_fib_width != (int)w || _fib_height != (int)h) { XFreePixmap (dpy, _pixbuffer); _pixbuffer = None; } } if (_pixbuffer == None) { XWindowAttributes wa; XGetWindowAttributes (dpy, realwin, &wa); _pixbuffer = XCreatePixmap (dpy, realwin, _fib_width, _fib_height, wa.depth); } #endif if (_pixbuffer != None) { XSetForeground (dpy, _fib_gc, _c_gray1.pixel); XFillRectangle (dpy, _pixbuffer, _fib_gc, 0, 0, _fib_width, _fib_height); } else { XSetForeground (dpy, _fib_gc, _c_gray1.pixel); XFillRectangle (dpy, realwin, _fib_gc, 0, 0, _fib_width, _fib_height); } _fib_resized = 0; } if (_pixbuffer == None) { win = realwin; } else { win = _pixbuffer; } // Top Row: dirs and up navigation int ppw = 0; int ppx = FAREAMRGB; for (i = _pathparts - 1; i >= 0; --i) { ppw += _pathbtn[i].xw + PSEP; if (ppw >= _fib_width - PSEP - _pathbtn[0].xw - FAREAMRGB) break; // XXX, first change is from "/" to "<", NOOP } ++i; // border-less "<" parent/up, IFF space is limited if (i > 0) { if (0 == _hov_p || (_hov_p > 0 && _hov_p < _pathparts - 1)) { XSetForeground (dpy, _fib_gc, _c_gray4.pixel); } else { XSetForeground (dpy, _fib_gc, blackColor); } XDrawString (dpy, win, _fib_gc, ppx, PATHBTNTOP, "<", 1); ppx += _pathbtn[0].xw + PSEP; if (i == _pathparts) --i; } _view_p = i; while (i < _pathparts) { if (i == _hov_p) { XSetForeground (dpy, _fib_gc, _c_gray0.pixel); } else { XSetForeground (dpy, _fib_gc, _c_gray2.pixel); } XFillRectangle (dpy, win, _fib_gc, ppx + 1, PATHBTNTOP - _fib_font_ascent, _pathbtn[i].xw - 1, _fib_font_height); VDrawRectangle (dpy, win, _fib_gc, ppx, PATHBTNTOP - _fib_font_ascent, _pathbtn[i].xw, _fib_font_height); XDrawString (dpy, win, _fib_gc, ppx + 1 + BTNPADDING, PATHBTNTOP, _pathbtn[i].name, strlen (_pathbtn[i].name)); _pathbtn[i].x0 = ppx; // current position ppx += _pathbtn[i].xw + PSEP; ++i; } // middle, scroll list of file names const int ltop = LISTTOP * _fib_font_vsep; const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; const int fsel_height = 4 + llen * _fib_font_vsep; const int fsel_width = _fib_width - FAREAMRGL - FAREAMRGR - (llen < _dircount ? SCROLLBARW : 0); const int t_x = FAREATEXTL; int t_s = FAREATEXTL + fsel_width; int t_t = FAREATEXTL + fsel_width; // check which colums can be visible // depending on available width of window. _columns = 0; if (fsel_width > FILECOLUMN + _fib_font_size_width + _fib_font_time_width) { _columns |= 2; t_s = FAREAMRGL + fsel_width - _fib_font_time_width - TEXTSEP; } if (fsel_width > FILECOLUMN + _fib_font_size_width) { _columns |= 1; t_t = t_s - _fib_font_size_width - TEXTSEP; } int fstop = _scrl_f; // first entry in scroll position const int ttop = ltop - _fib_font_height + _fib_font_ascent; if (fstop > 0 && fstop + llen > _dircount) { fstop = MAX (0, _dircount - llen); _scrl_f = fstop; } // list header XSetForeground (dpy, _fib_gc, _c_gray3.pixel); XFillRectangle (dpy, win, _fib_gc, FAREAMRGL, ltop - _fib_font_vsep, fsel_width, _fib_font_vsep); // draw background of file list XSetForeground (dpy, _fib_gc, _c_gray2.pixel); XFillRectangle (dpy, win, _fib_gc, FAREAMRGL, ltop, fsel_width, fsel_height); #ifdef DRAW_OUTLINE VDrawRectangle (dpy, win, _fib_gc, FAREAMRGL, ltop - _fib_font_vsep -1, _fib_width - FAREAMRGL - FAREAMRGR, fsel_height + _fib_font_vsep + 1); #endif switch (_hov_h) { case 1: XSetForeground (dpy, _fib_gc, _c_gray0.pixel); XFillRectangle (dpy, win, _fib_gc, t_x + _fib_dir_indent - TEXTSEP + 1, ltop - _fib_font_vsep, t_t - t_x - _fib_dir_indent - 1, _fib_font_vsep); break; case 2: XSetForeground (dpy, _fib_gc, _c_gray0.pixel); XFillRectangle (dpy, win, _fib_gc, t_t - TEXTSEP + 1, ltop - _fib_font_vsep, _fib_font_size_width + TEXTSEP - 1, _fib_font_vsep); break; case 3: XSetForeground (dpy, _fib_gc, _c_gray0.pixel); XFillRectangle (dpy, win, _fib_gc, t_s - TEXTSEP + 1, ltop - _fib_font_vsep, TEXTSEP + TEXTSEP + _fib_font_time_width - 1, _fib_font_vsep); break; default: break; } // column headings and sort order int arp = MAX (2, _fib_font_height / 5); // arrow scale const int trioff = _fib_font_height - _fib_font_ascent - arp + 1; XPoint ptri[4] = { {0, ttop - trioff }, {arp, -arp - arp - 1}, {-arp - arp, 0}, {arp, arp + arp + 1}}; if (_sort & 1) { ptri[0].y = ttop -arp - arp - 1; ptri[1].y *= -1; ptri[3].y *= -1; } switch (_sort) { case 0: case 1: ptri[0].x = t_t + SORTBTNOFF + 2 - arp; XSetForeground (dpy, _fib_gc, _c_gray6.pixel); XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); break; case 2: case 3: if (_columns & 1) { ptri[0].x = t_s + SORTBTNOFF + 2 - arp; XSetForeground (dpy, _fib_gc, _c_gray6.pixel); XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); } break; case 4: case 5: if (_columns & 2) { ptri[0].x = FAREATEXTL + fsel_width + SORTBTNOFF + 2 - arp; XSetForeground (dpy, _fib_gc, _c_gray6.pixel); XFillPolygon (dpy, win, _fib_gc, ptri, 3, Convex, CoordModePrevious); XDrawLines (dpy, win, _fib_gc, ptri, 4, CoordModePrevious); } break; } #if 0 // bottom header bottom border XSetForeground (dpy, _fib_gc, _c_gray5.pixel); XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); XDrawLine (dpy, win, _fib_gc, FAREAMRGL + 1, ltop, FAREAMRGL + fsel_width, ltop); XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); #endif XSetForeground (dpy, _fib_gc, _c_gray4.pixel); XDrawLine (dpy, win, _fib_gc, t_x + _fib_dir_indent - TEXTSEP, ltop - _fib_font_vsep + 3, t_x + _fib_dir_indent - TEXTSEP, ltop - 3); XSetForeground (dpy, _fib_gc, blackColor); XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, ttop, "Name", 4); if (_columns & 1) { XSetForeground (dpy, _fib_gc, _c_gray4.pixel); XDrawLine (dpy, win, _fib_gc, t_t - TEXTSEP, ltop - _fib_font_vsep + 3, t_t - TEXTSEP, ltop - 3); XSetForeground (dpy, _fib_gc, blackColor); XDrawString (dpy, win, _fib_gc, t_t, ttop, "Size", 4); } if (_columns & 2) { XSetForeground (dpy, _fib_gc, _c_gray4.pixel); XDrawLine (dpy, win, _fib_gc, t_s - TEXTSEP, ltop - _fib_font_vsep + 3, t_s - TEXTSEP, ltop - 3); XSetForeground (dpy, _fib_gc, blackColor); if (_pathparts > 0) XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Modified", 13); else XDrawString (dpy, win, _fib_gc, t_s, ttop, "Last Used", 9); } // scrollbar sep if (llen < _dircount) { const int sx0 = _fib_width - SCROLLBARW - FAREAMRGR; XSetForeground (dpy, _fib_gc, _c_gray4.pixel); XDrawLine (dpy, win, _fib_gc, sx0 - 1, ltop - _fib_font_vsep, #ifdef DRAW_OUTLINE sx0 - 1, ltop + fsel_height #else sx0 - 1, ltop - 1 #endif ); } // clip area for file-name XRectangle clp = {FAREAMRGL + 1, ltop, t_t - FAREAMRGL - TEXTSEP - TEXTSEP - 1, fsel_height}; // list files in view for (i = 0; i < llen; ++i) { const int j = i + fstop; if (j >= _dircount) break; const int t_y = ltop + (i+1) * _fib_font_vsep - 4; XSetForeground (dpy, _fib_gc, blackColor); if (_dirlist[j].flags & 2) { XSetForeground (dpy, _fib_gc, blackColor); XFillRectangle (dpy, win, _fib_gc, FAREAMRGL, t_y - _fib_font_ascent, fsel_width, _fib_font_height); XSetForeground (dpy, _fib_gc, whiteColor); } if (_hov_f == j && !(_dirlist[j].flags & 2)) { XSetForeground (dpy, _fib_gc, _c_gray4.pixel); } if (_dirlist[j].flags & 4) { XDrawString (dpy, win, _fib_gc, t_x, t_y, "D", 1); } XSetClipRectangles (dpy, _fib_gc, 0, 0, &clp, 1, Unsorted); XDrawString (dpy, win, _fib_gc, t_x + _fib_dir_indent, t_y, _dirlist[j].name, strlen (_dirlist[j].name)); XSetClipMask (dpy, _fib_gc, None); if (_columns & 1) // right-aligned 'size' XDrawString (dpy, win, _fib_gc, t_s - TEXTSEP - 2 - _dirlist[j].ssizew, t_y, _dirlist[j].strsize, strlen (_dirlist[j].strsize)); if (_columns & 2) XDrawString (dpy, win, _fib_gc, t_s, t_y, _dirlist[j].strtime, strlen (_dirlist[j].strtime)); } // scrollbar if (llen < _dircount) { float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; sl = MAX ((8. / llen), sl); // 8px min height of scroller const int sy1 = llen * sl; const float mx = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH) - sy1) / (float)(_dircount - llen); const int sy0 = fstop * mx; const int sx0 = _fib_width - SCROLLBARW - FAREAMRGR; const int stop = ltop - _fib_font_vsep; _scrl_y0 = stop + SCROLLBOXH + sy0; _scrl_y1 = _scrl_y0 + sy1; assert (fstop + llen <= _dircount); // scroll-bar background XSetForeground (dpy, _fib_gc, _c_gray3.pixel); XFillRectangle (dpy, win, _fib_gc, sx0, stop, SCROLLBARW, fsel_height + _fib_font_vsep); // scroller if (_hov_s == 0) { XSetForeground (dpy, _fib_gc, _c_gray0.pixel); } else { XSetForeground (dpy, _fib_gc, _c_gray1.pixel); } XFillRectangle (dpy, win, _fib_gc, sx0 + 1, stop + SCROLLBOXH + sy0, SCROLLBARW - 2, sy1); int scrw = (SCROLLBARW -3) / 2; // arrows top and bottom if (_hov_s == 1) { XSetForeground (dpy, _fib_gc, _c_gray0.pixel); } else { XSetForeground (dpy, _fib_gc, _c_gray1.pixel); } XPoint ptst[4] = { {sx0 + 1, stop + 8}, {scrw, -7}, {scrw, 7}, {-2 * scrw, 0}}; XFillPolygon (dpy, win, _fib_gc, ptst, 3, Convex, CoordModePrevious); XDrawLines (dpy, win, _fib_gc, ptst, 4, CoordModePrevious); if (_hov_s == 2) { XSetForeground (dpy, _fib_gc, _c_gray0.pixel); } else { XSetForeground (dpy, _fib_gc, _c_gray1.pixel); } XPoint ptsb[4] = { {sx0 + 1, ltop + fsel_height - 9}, {2*scrw, 0}, {-scrw, 7}, {-scrw, -7}}; XFillPolygon (dpy, win, _fib_gc, ptsb, 3, Convex, CoordModePrevious); XDrawLines (dpy, win, _fib_gc, ptsb, 4, CoordModePrevious); } else { _scrl_y0 = _scrl_y1 = -1; } if (_fib_show_places) { assert (_placecnt > 0); // heading XSetForeground (dpy, _fib_gc, _c_gray3.pixel); XFillRectangle (dpy, win, _fib_gc, FAREAMRGB, ltop - _fib_font_vsep, PLACESW - TEXTSEP, _fib_font_vsep); // body XSetForeground (dpy, _fib_gc, _c_gray2.pixel); XFillRectangle (dpy, win, _fib_gc, FAREAMRGB, ltop, PLACESW - TEXTSEP, fsel_height); #ifdef DRAW_OUTLINE VDrawRectangle (dpy, win, _fib_gc, FAREAMRGB, ltop - _fib_font_vsep -1, PLACESW - TEXTSEP, fsel_height + _fib_font_vsep + 1); #endif XSetForeground (dpy, _fib_gc, blackColor); XDrawString (dpy, win, _fib_gc, FAREAMRGB + TEXTSEP, ttop, "Places", 6); XRectangle pclip = {FAREAMRGB + 1, ltop, PLACESW - TEXTSEP -1, fsel_height}; XSetClipRectangles (dpy, _fib_gc, 0, 0, &pclip, 1, Unsorted); const int plx = FAREAMRGB + TEXTSEP; for (i = 0; i < llen && i < _placecnt; ++i) { const int ply = ltop + (i+1) * _fib_font_vsep - 4; if (i == _hov_l) { XSetForeground (dpy, _fib_gc, _c_gray4.pixel); } else { XSetForeground (dpy, _fib_gc, blackColor); } XDrawString (dpy, win, _fib_gc, plx, ply, _placelist[i].name, strlen (_placelist[i].name)); if (_placelist[i].flags & 4) { XSetForeground (dpy, _fib_gc, _c_gray3.pixel); const int plly = ply - _fib_font_ascent + _fib_font_height; const int pllx0 = FAREAMRGB; const int pllx1 = FAREAMRGB + (PLACESW - TEXTSEP); XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); } } XSetClipMask (dpy, _fib_gc, None); if (_placecnt > llen) { const int plly = ltop + fsel_height - _fib_font_height + _fib_font_ascent; const int pllx0 = FAREAMRGB + (PLACESW - TEXTSEP) * .75; const int pllx1 = FAREAMRGB + (PLACESW - TEXTSEP - TEXTSEP); XSetForeground (dpy, _fib_gc, blackColor); XSetLineAttributes (dpy, _fib_gc, 1, LineOnOffDash, CapButt, JoinMiter); XDrawLine (dpy, win, _fib_gc, pllx0, plly, pllx1, plly); XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); } } // Bottom Buttons const int numb = sizeof(_btns) / sizeof(FibButton*); int xtra = _fib_width - _btn_span; const int cbox = _fib_font_ascent - 2; const int bbase = _fib_height - BTNBTMMARGIN * _fib_font_vsep - BTNPADDING; const int cblw = cbox > 20 ? 5 : ( cbox > 9 ? 3 : 1); int bx = FAREAMRGB; for (i = 0; i < numb; ++i) { if (_btns[i]->flags & 8) { continue; } if (_btns[i]->flags & 4) { // checkbutton const int cby0 = bbase - cbox + 1 + BTNPADDING; if (i == _hov_b) { XSetForeground (dpy, _fib_gc, _c_gray4.pixel); } else { XSetForeground (dpy, _fib_gc, blackColor); } XDrawRectangle (dpy, win, _fib_gc, bx, cby0 - 1, cbox + 1, cbox + 1); if (i == _hov_b) { XSetForeground (dpy, _fib_gc, _c_gray5.pixel); } else { XSetForeground (dpy, _fib_gc, blackColor); } XDrawString (dpy, win, _fib_gc, BTNPADDING + bx + _fib_font_ascent, 1 + bbase + BTNPADDING, _btns[i]->text, strlen (_btns[i]->text)); if (i == _hov_b) { XSetForeground (dpy, _fib_gc, _c_gray0.pixel); } else { if (_btns[i]->flags & 2) { XSetForeground (dpy, _fib_gc, _c_gray1.pixel); } else { XSetForeground (dpy, _fib_gc, _c_gray2.pixel); } } XFillRectangle (dpy, win, _fib_gc, bx+1, cby0, cbox, cbox); if (_btns[i]->flags & 2) { XSetLineAttributes (dpy, _fib_gc, cblw, LineSolid, CapRound, JoinMiter); XSetForeground (dpy, _fib_gc, _c_gray6.pixel); XDrawLine (dpy, win, _fib_gc, bx + 2, cby0 + 1, bx + cbox - 1, cby0 + cbox - 2); XDrawLine (dpy, win, _fib_gc, bx + cbox - 1, cby0 + 1, bx + 2, cby0 + cbox - 2); XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); } } else { if (xtra > 0) { bx += xtra; xtra = 0; } // pushbutton uint8_t can_hover = 1; // special case if (_btns[i] == &_btn_ok) { if (_fsel < 0 || _fsel >= _dircount) { can_hover = 0; } } if (can_hover && i == _hov_b) { XSetForeground (dpy, _fib_gc, _c_gray0.pixel); } else { XSetForeground (dpy, _fib_gc, _c_gray2.pixel); } XFillRectangle (dpy, win, _fib_gc, bx + 1, bbase - _fib_font_ascent, _btn_w - 1, _fib_font_height + BTNPADDING + BTNPADDING); VDrawRectangle (dpy, win, _fib_gc, bx, bbase - _fib_font_ascent, _btn_w, _fib_font_height + BTNPADDING + BTNPADDING); XDrawString (dpy, win, _fib_gc, bx + (_btn_w - _btns[i]->tw) * .5, 1 + bbase + BTNPADDING, _btns[i]->text, strlen (_btns[i]->text)); } _btns[i]->x0 = bx; bx += _btns[i]->xw + DSEP; } if (_pixbuffer != None) { XCopyArea(dpy, _pixbuffer, realwin, _fib_gc, 0, 0, _fib_width, _fib_height, 0, 0); } XFlush (dpy); } static void fib_reset () { _hov_p = _hov_f = _hov_h = _hov_l = -1; _scrl_f = 0; _fib_resized = 1; } static int cmp_n_up (const void *p1, const void *p2) { FibFileEntry *a = (FibFileEntry*) p1; FibFileEntry *b = (FibFileEntry*) p2; if ((a->flags & 4) && !(b->flags & 4)) return -1; if (!(a->flags & 4) && (b->flags & 4)) return 1; return strcmp (a->name, b->name); } static int cmp_n_down (const void *p1, const void *p2) { FibFileEntry *a = (FibFileEntry*) p1; FibFileEntry *b = (FibFileEntry*) p2; if ((a->flags & 4) && !(b->flags & 4)) return -1; if (!(a->flags & 4) && (b->flags & 4)) return 1; return strcmp (b->name, a->name); } static int cmp_t_up (const void *p1, const void *p2) { FibFileEntry *a = (FibFileEntry*) p1; FibFileEntry *b = (FibFileEntry*) p2; if ((a->flags & 4) && !(b->flags & 4)) return -1; if (!(a->flags & 4) && (b->flags & 4)) return 1; if (a->mtime == b->mtime) return 0; return a->mtime > b->mtime ? -1 : 1; } static int cmp_t_down (const void *p1, const void *p2) { FibFileEntry *a = (FibFileEntry*) p1; FibFileEntry *b = (FibFileEntry*) p2; if ((a->flags & 4) && !(b->flags & 4)) return -1; if (!(a->flags & 4) && (b->flags & 4)) return 1; if (a->mtime == b->mtime) return 0; return a->mtime > b->mtime ? 1 : -1; } static int cmp_s_up (const void *p1, const void *p2) { FibFileEntry *a = (FibFileEntry*) p1; FibFileEntry *b = (FibFileEntry*) p2; if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order if ((a->flags & 4) && !(b->flags & 4)) return -1; if (!(a->flags & 4) && (b->flags & 4)) return 1; if (a->size == b->size) return 0; return a->size > b->size ? -1 : 1; } static int cmp_s_down (const void *p1, const void *p2) { FibFileEntry *a = (FibFileEntry*) p1; FibFileEntry *b = (FibFileEntry*) p2; if ((a->flags & 4) && (b->flags & 4)) return 0; // dir, no size, retain order if ((a->flags & 4) && !(b->flags & 4)) return -1; if (!(a->flags & 4) && (b->flags & 4)) return 1; if (a->size == b->size) return 0; return a->size > b->size ? 1 : -1; } static void fmt_size (Display *dpy, FibFileEntry *f) { if (f->size > 10995116277760) { sprintf (f->strsize, "%.0f TB", f->size / 1099511627776.f); } if (f->size > 1099511627776) { sprintf (f->strsize, "%.1f TB", f->size / 1099511627776.f); } else if (f->size > 10737418240) { sprintf (f->strsize, "%.0f GB", f->size / 1073741824.f); } else if (f->size > 1073741824) { sprintf (f->strsize, "%.1f GB", f->size / 1073741824.f); } else if (f->size > 10485760) { sprintf (f->strsize, "%.0f MB", f->size / 1048576.f); } else if (f->size > 1048576) { sprintf (f->strsize, "%.1f MB", f->size / 1048576.f); } else if (f->size > 10240) { sprintf (f->strsize, "%.0f KB", f->size / 1024.f); } else if (f->size >= 1000) { sprintf (f->strsize, "%.1f KB", f->size / 1024.f); } else { sprintf (f->strsize, "%.0f B", f->size / 1.f); } int sw = 0; query_font_geometry (dpy, _fib_gc, f->strsize, &sw, NULL, NULL, NULL); if (sw > _fib_font_size_width) { _fib_font_size_width = sw; } f->ssizew = sw; } static void fmt_time (Display *dpy, FibFileEntry *f) { struct tm *tmp; tmp = localtime (&f->mtime); if (!tmp) { return; } strftime (f->strtime, sizeof(f->strtime), "%F %H:%M", tmp); int tw = 0; query_font_geometry (dpy, _fib_gc, f->strtime, &tw, NULL, NULL, NULL); if (tw > _fib_font_time_width) { _fib_font_time_width = tw; } } static void fib_resort (const char * sel) { if (_dircount < 1) { return; } int (*sortfn)(const void *p1, const void *p2); switch (_sort) { case 1: sortfn = &cmp_n_down; break; case 2: sortfn = &cmp_s_down; break; case 3: sortfn = &cmp_s_up; break; case 4: sortfn = &cmp_t_down; break; case 5: sortfn = &cmp_t_up; break; default: sortfn = &cmp_n_up; break; } qsort (_dirlist, _dircount, sizeof(_dirlist[0]), sortfn); int i; for (i = 0; i < _dircount && sel; ++i) { if (!strcmp (_dirlist[i].name, sel)) { _fsel = i; break; } } } static void fib_select (Display *dpy, int item) { if (_fsel >= 0) { _dirlist[_fsel].flags &= ~2; } _fsel = item; if (_fsel >= 0 && _fsel < _dircount) { _dirlist[_fsel].flags |= 2; const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; if (_fsel < _scrl_f) { _scrl_f = _fsel; } else if (_fsel >= _scrl_f + llen) { _scrl_f = 1 + _fsel - llen; } } else { _fsel = -1; } fib_expose (dpy, _fib_win); } static inline int fib_filter (const char *name) { if (_fib_filter_function) { return _fib_filter_function (name); } else { return 1; } } static void fib_pre_opendir (Display *dpy) { if (_dirlist) free (_dirlist); if (_pathbtn) free (_pathbtn); _dirlist = NULL; _pathbtn = NULL; _dircount = 0; _pathparts = 0; query_font_geometry (dpy, _fib_gc, "Size ", &_fib_font_size_width, NULL, NULL, NULL); fib_reset (); _fsel = -1; } static void fib_post_opendir (Display *dpy, const char *sel) { if (_dircount > 0) _fsel = 0; // select first else _fsel = -1; fib_resort (sel); if (_dircount > 0 && _fsel >= 0) { fib_select (dpy, _fsel); } else { fib_expose (dpy, _fib_win); } } static int fib_dirlistadd (Display *dpy, const int i, const char* path, const char *name, time_t mtime) { char tp[1024]; struct stat fs; if (!_fib_hidden_fn && name[0] == '.') return -1; if (!strcmp (name, ".")) return -1; if (!strcmp (name, "..")) return -1; strcpy (tp, path); strcat (tp, name); if (access (tp, R_OK)) { return -1; } if (stat (tp, &fs)) { return -1; } assert (i < _dircount); // could happen if dir changes while we're reading. if (i >= _dircount) return -1; if (S_ISDIR (fs.st_mode)) { _dirlist[i].flags |= 4; } else if (S_ISREG (fs.st_mode)) { if (!fib_filter (name)) return -1; } #if 0 // only needed with lstat() else if (S_ISLNK (fs.st_mode)) { if (!fib_filter (name)) return -1; } #endif else { return -1; } strcpy (_dirlist[i].name, name); _dirlist[i].mtime = mtime > 0 ? mtime : fs.st_mtime; _dirlist[i].size = fs.st_size; if (!(_dirlist[i].flags & 4)) fmt_size (dpy, &_dirlist[i]); fmt_time (dpy, &_dirlist[i]); return 0; } static int fib_openrecent (Display *dpy, const char *sel) { int i; unsigned int j; assert (_recentcnt > 0); fib_pre_opendir (dpy); query_font_geometry (dpy, _fib_gc, "Last Used", &_fib_font_time_width, NULL, NULL, NULL); _dirlist = (FibFileEntry*) calloc (_recentcnt, sizeof(FibFileEntry)); _dircount = _recentcnt; for (j = 0, i = 0; j < _recentcnt; ++j) { char base[1024]; char *s = strrchr (_recentlist[j].path, '/'); if (!s || !*++s) continue; size_t len = (s - _recentlist[j].path); strncpy (base, _recentlist[j].path, len); base[len] = '\0'; if (!fib_dirlistadd (dpy, i, base, s, _recentlist[j].atime)) { _dirlist[i].rfp = &_recentlist[j]; _dirlist[i].flags |= 8; ++i; } } _dircount = i; fib_post_opendir (dpy, sel); return _dircount; } static int fib_opendir (Display *dpy, const char* path, const char *sel) { char *t0, *t1; int i; assert (path); if (strlen (path) == 0 && _recentcnt > 0) { // XXX we should use a better indication for this strcpy (_cur_path, ""); return fib_openrecent (dpy, sel); } assert (strlen (path) < sizeof(_cur_path) -1); assert (strlen (path) > 0); assert (strstr (path, "//") == NULL); assert (path[0] == '/'); fib_pre_opendir (dpy); query_font_geometry (dpy, _fib_gc, "Last Modified", &_fib_font_time_width, NULL, NULL, NULL); DIR *dir = opendir (path); if (!dir) { strcpy (_cur_path, "/"); } else { int i; struct dirent *de; strcpy (_cur_path, path); if (_cur_path[strlen (_cur_path) -1] != '/') strcat (_cur_path, "/"); while ((de = readdir (dir))) { if (!_fib_hidden_fn && de->d_name[0] == '.') continue; ++_dircount; } if (_dircount > 0) _dirlist = (FibFileEntry*) calloc (_dircount, sizeof(FibFileEntry)); rewinddir (dir); i = 0; while ((de = readdir (dir))) { if (!fib_dirlistadd (dpy, i, _cur_path, de->d_name, 0)) ++i; } _dircount = i; closedir (dir); } t0 = _cur_path; while (*t0 && (t0 = strchr (t0, '/'))) { ++_pathparts; ++t0; } assert (_pathparts > 0); _pathbtn = (FibPathButton*) calloc (_pathparts + 1, sizeof(FibPathButton)); t1 = _cur_path; i = 0; while (*t1 && (t0 = strchr (t1, '/'))) { if (i == 0) { strcpy (_pathbtn[i].name, "/"); } else { *t0 = 0; strcpy (_pathbtn[i].name, t1); } query_font_geometry (dpy, _fib_gc, _pathbtn[i].name, &_pathbtn[i].xw, NULL, NULL, NULL); _pathbtn[i].xw += BTNPADDING + BTNPADDING; *t0 = '/'; t1 = t0 + 1; ++i; } fib_post_opendir (dpy, sel); return _dircount; } static int fib_open (Display *dpy, int item) { char tp[1024]; if (_dirlist[item].flags & 8) { assert (_dirlist[item].rfp); strcpy (_rv_open, _dirlist[item].rfp->path); _status = 1; return 0; } strcpy (tp, _cur_path); strcat (tp, _dirlist[item].name); if (_dirlist[item].flags & 4) { fib_opendir (dpy, tp, NULL); return 0; } else { _status = 1; strcpy (_rv_open, tp); } return 0; } static void cb_cancel (Display *dpy) { _status = -1; } static void cb_open (Display *dpy) { if (_fsel >= 0 && _fsel < _dircount) { fib_open (dpy, _fsel); } } static void sync_button_states () { if (_fib_show_places) _btn_places.flags |= 2; else _btn_places.flags &= ~2; if (_fib_filter_fn) // inverse -> show all _btn_filter.flags &= ~2; else _btn_filter.flags |= 2; if (_fib_hidden_fn) _btn_hidden.flags |= 2; else _btn_hidden.flags &= ~2; } static void cb_places (Display *dpy) { _fib_show_places = ! _fib_show_places; if (_placecnt < 1) _fib_show_places = 0; sync_button_states (); _fib_resized = 1; fib_expose (dpy, _fib_win); } static void cb_filter (Display *dpy) { _fib_filter_fn = ! _fib_filter_fn; sync_button_states (); char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; fib_opendir (dpy, _cur_path, sel); free (sel); } static void cb_hidden (Display *dpy) { _fib_hidden_fn = ! _fib_hidden_fn; sync_button_states (); char *sel = _fsel >= 0 ? strdup (_dirlist[_fsel].name) : NULL; fib_opendir (dpy, _cur_path, sel); free (sel); } static int fib_widget_at_pos (Display *dpy, int x, int y, int *it) { const int btop = _fib_height - BTNBTMMARGIN * _fib_font_vsep - _fib_font_ascent - BTNPADDING; const int bbot = btop + _fib_font_height + BTNPADDING + BTNPADDING; const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; const int ltop = LISTTOP * _fib_font_vsep; const int fbot = ltop + 4 + llen * _fib_font_vsep; const int ptop = PATHBTNTOP - _fib_font_ascent; assert (it); // paths at top if (y > ptop && y < ptop + _fib_font_height && _view_p >= 0 && _pathparts > 0) { int i = _view_p; *it = -1; if (i > 0) { // special case '<' if (x > FAREAMRGB && x <= FAREAMRGB + _pathbtn[0].xw) { *it = _view_p - 1; i = _pathparts; } } while (i < _pathparts) { if (x >= _pathbtn[i].x0 && x <= _pathbtn[i].x0 + _pathbtn[i].xw) { *it = i; break; } ++i; } assert (*it < _pathparts); if (*it >= 0) return 1; else return 0; } // buttons at bottom if (y > btop && y < bbot) { size_t i; *it = -1; for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { const int bx = _btns[i]->x0; if (_btns[i]->flags & 8) { continue; } if (x > bx && x < bx + _btns[i]->xw) { *it = i; } } if (*it >= 0) return 3; else return 0; } // main file area if (y >= ltop - _fib_font_vsep && y < fbot && x > FAREAMRGL && x < _fib_width - FAREAMRGR) { // scrollbar if (_scrl_y0 > 0 && x >= _fib_width - (FAREAMRGR + SCROLLBARW) && x <= _fib_width - FAREAMRGR) { if (y >= _scrl_y0 && y < _scrl_y1) { *it = 0; } else if (y >= _scrl_y1) { *it = 2; } else { *it = 1; } return 4; } // file-list else if (y >= ltop) { const int item = (y - ltop) / _fib_font_vsep + _scrl_f; *it = -1; if (item >= 0 && item < _dircount) { *it = item; } if (*it >= 0) return 2; else return 0; } else { *it = -1; const int fsel_width = _fib_width - FAREAMRGL - FAREAMRGR - (llen < _dircount ? SCROLLBARW : 0); const int t_s = FAREAMRGL + fsel_width - _fib_font_time_width - TEXTSEP - TEXTSEP; const int t_t = FAREAMRGL + fsel_width - TEXTSEP - _fib_font_size_width - ((_columns & 2) ? ( _fib_font_time_width + TEXTSEP + TEXTSEP) : 0); if (x >= fsel_width + FAREAMRGL) ; else if ((_columns & 2) && x >= t_s) *it = 3; else if ((_columns & 1) && x >= t_t) *it = 2; else if (x >= FAREATEXTL + _fib_dir_indent - TEXTSEP) *it = 1; if (*it >= 0) return 5; else return 0; } } // places list if (_fib_show_places && y >= ltop && y < fbot && x > FAREAMRGB && x < FAREAMRGL - FAREAMRGB) { const int item = (y - ltop) / _fib_font_vsep; *it = -1; if (item >= 0 && item < _placecnt) { *it = item; } if (*it >= 0) return 6; else return 0; } return 0; } static void fib_update_hover (Display *dpy, int need_expose, const int type, const int item) { int hov_p = -1; int hov_b = -1; int hov_h = -1; int hov_s = -1; #ifdef LIST_ENTRY_HOVER int hov_f = -1; int hov_l = -1; #endif switch (type) { case 1: hov_p = item; break; case 3: hov_b = item; break; case 4: hov_s = item; break; case 5: hov_h = item; break; #ifdef LIST_ENTRY_HOVER case 6: hov_l = item; break; case 2: hov_f = item; break; #endif default: break; } #ifdef LIST_ENTRY_HOVER if (hov_f != _hov_f) { _hov_f = hov_f; need_expose = 1; } if (hov_l != _hov_l) { _hov_l = hov_l; need_expose = 1; } #endif if (hov_b != _hov_b) { _hov_b = hov_b; need_expose = 1; } if (hov_p != _hov_p) { _hov_p = hov_p; need_expose = 1; } if (hov_h != _hov_h) { _hov_h = hov_h; need_expose = 1; } if (hov_s != _hov_s) { _hov_s = hov_s; need_expose = 1; } if (need_expose) { fib_expose (dpy, _fib_win); } } static void fib_motion (Display *dpy, int x, int y) { int it = -1; if (_scrl_my >= 0) { const int sdiff = y - _scrl_my; const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; const int fsel_height = 4 + llen * _fib_font_vsep; const float sl = (fsel_height + _fib_font_vsep - (SCROLLBOXH + SCROLLBOXH)) / (float) _dircount; int news = _scrl_mf + sdiff / sl; if (news < 0) news = 0; if (news >= (_dircount - llen)) news = _dircount - llen; if (news != _scrl_f) { _scrl_f = news; fib_expose (dpy, _fib_win); } return; } const int type = fib_widget_at_pos (dpy, x, y, &it); fib_update_hover (dpy, 0, type, it); } static void fib_mousedown (Display *dpy, int x, int y, int btn, unsigned long time) { int it; switch (fib_widget_at_pos (dpy, x, y, &it)) { case 4: // scrollbar if (btn == 1) { _dblclk = 0; if (it == 0) { _scrl_my = y; _scrl_mf = _scrl_f; } else { int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; if (llen < 2) llen = 2; int news = _scrl_f; if (it == 1) { news -= llen - 1; } else { news += llen - 1; } if (news < 0) news = 0; if (news >= (_dircount - llen)) news = _dircount - llen; if (news != _scrl_f && _scrl_y0 >= 0) { assert (news >=0); _scrl_f = news; fib_update_hover (dpy, 1, 4, it); } } } break; case 2: // file-list if (btn == 4 || btn == 5) { const int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; int news = _scrl_f + ((btn == 4) ? - 1 : 1); if (news < 0) news = 0; if (news >= (_dircount - llen)) news = _dircount - llen; if (news != _scrl_f && _scrl_y0 >= 0) { assert (news >=0); _scrl_f = news; fib_update_hover (dpy, 1, 0, 0); } _dblclk = 0; } else if (btn == 1 && it >= 0 && it < _dircount) { if (_fsel == it) { if (time - _dblclk < DBLCLKTME) { fib_open (dpy, it); _dblclk = 0; } _dblclk = time; } else { fib_select (dpy, it); _dblclk = time; } if (_fsel >= 0) { if (!(_dirlist[_fsel].flags & 4)); } } break; case 1: // paths assert (_fsel < _dircount); assert (it >= 0 && it < _pathparts); { int i = 0; char path[1024] = "/"; while (++i <= it) { strcat (path, _pathbtn[i].name); strcat (path, "/"); } char *sel = NULL; if (i < _pathparts) sel = strdup (_pathbtn[i].name); else if (i == _pathparts && _fsel >= 0) sel = strdup (_dirlist[_fsel].name); fib_opendir (dpy, path, sel); free (sel); } break; case 3: // btn if (btn == 1 && _btns[it]->callback) { _btns[it]->callback (dpy); } break; case 5: // sort if (btn == 1) { switch (it) { case 1: if (_sort == 0) _sort = 1; else _sort = 0; break; case 2: if (_sort == 2) _sort = 3; else _sort = 2; break; case 3: if (_sort == 4) _sort = 5; else _sort = 4; break; } if (_fsel >= 0) { assert (_dirlist && _dircount >= _fsel); _dirlist[_fsel].flags &= ~2; char *sel = strdup (_dirlist[_fsel].name); fib_resort (sel); free (sel); } else { fib_resort (NULL); _fsel = -1; } fib_reset (); _hov_h = it; fib_select (dpy, _fsel); } break; case 6: if (btn == 1 && it >= 0 && it < _placecnt) { fib_opendir (dpy, _placelist[it].path, NULL); } break; default: break; } } static void fib_mouseup (Display *dpy, int x, int y, int btn, unsigned long time) { _scrl_my = -1; } static void add_place_raw (Display *dpy, const char *name, const char *path) { _placelist = (FibPlace*) realloc (_placelist, (_placecnt + 1) * sizeof(FibPlace)); strcpy (_placelist[_placecnt].path, path); strcpy (_placelist[_placecnt].name, name); _placelist[_placecnt].flags = 0; int sw; query_font_geometry (dpy, _fib_gc, name, &sw, NULL, NULL, NULL); if (sw > _fib_place_width) { _fib_place_width = sw; } ++_placecnt; } static int add_place_places (Display *dpy, const char *name, const char *url) { char const * path; struct stat fs; int i; if (!url || strlen (url) < 1) return -1; if (!name || strlen (name) < 1) return -1; if (url[0] == '/') { path = url; } else if (!strncmp (url, "file:///", 8)) { path = &url[7]; } else { return -1; } if (access (path, R_OK)) { return -1; } if (stat (path, &fs)) { return -1; } if (!S_ISDIR (fs.st_mode)) { return -1; } for (i = 0; i < _placecnt; ++i) { if (!strcmp (path, _placelist[i].path)) { return -1; } } add_place_raw (dpy, name, path); return 0; } static int parse_gtk_bookmarks (Display *dpy, const char *fn) { char tmp[1024]; if (access (fn, R_OK)) { return -1; } FILE *bm = fopen (fn, "r"); if (!bm) return -1; int found = 0; while (fgets (tmp, sizeof(tmp), bm) && strlen (tmp) > 1 && strlen (tmp) < sizeof(tmp)) { char *s, *n; tmp[strlen (tmp) - 1] = '\0'; // strip newline if ((s = strchr (tmp, ' '))) { *s = '\0'; n = strdup (++s); decode_3986 (tmp); if (!add_place_places (dpy, n, tmp)) { ++found; } free (n); } else if ((s = strrchr (tmp, '/'))) { n = strdup (++s); decode_3986 (tmp); if (!add_place_places (dpy, n, tmp)) { ++found; } free (n); } } fclose (bm); return found; } static const char *ignore_mountpoints[] = { "/bin", "/boot", "/dev", "/etc", "/lib", "/live", "/mnt", "/opt", "/root", "/sbin", "/srv", "/tmp", "/usr", "/var", "/proc", "/sbin", "/net", "/sys" }; static const char *ignore_fs[] = { "auto", "autofs", "debugfs", "devfs", "devpts", "ecryptfs", "fusectl", "kernfs", "linprocfs", "proc", "ptyfs", "rootfs", "selinuxfs", "sysfs", "tmpfs", "usbfs", "nfsd", "rpc_pipefs", }; static const char *ignore_devices[] = { "binfmt_", "devpts", "gvfs", "none", "nfsd", "sunrpc", "/dev/loop", "/dev/vn" }; static int check_mount (const char *mountpoint, const char *fs, const char *device) { size_t i; if (!mountpoint || !fs || !device) return -1; //printf("%s %s %s\n", mountpoint, fs, device); for (i = 0 ; i < sizeof(ignore_mountpoints) / sizeof(char*); ++i) { if (!strncmp (mountpoint, ignore_mountpoints[i], strlen (ignore_mountpoints[i]))) { return 1; } } if (!strncmp (mountpoint, "/home", 5)) { return 1; } for (i = 0 ; i < sizeof(ignore_fs) / sizeof(char*); ++i) { if (!strncmp (fs, ignore_fs[i], strlen (ignore_fs[i]))) { return 1; } } for (i = 0 ; i < sizeof(ignore_devices) / sizeof(char*); ++i) { if (!strncmp (device, ignore_devices[i], strlen (ignore_devices[i]))) { return 1; } } return 0; } static int read_mtab (Display *dpy, const char *mtab) { FILE *mt = fopen (mtab, "r"); if (!mt) return -1; int found = 0; struct mntent *mntent; while ((mntent = getmntent (mt)) != NULL) { char *s; if (check_mount (mntent->mnt_dir, mntent->mnt_type, mntent->mnt_fsname)) continue; if ((s = strrchr (mntent->mnt_dir, '/'))) { ++s; } else { s = mntent->mnt_dir; } if (!add_place_places (dpy, s, mntent->mnt_dir)) { ++found; } } fclose (mt); return found; } static void populate_places (Display *dpy) { char tmp[1024]; int spacer = -1; if (_placecnt > 0) return; _fib_place_width = 0; if (_recentcnt > 0) { add_place_raw (dpy, "Recently Used", ""); _placelist[0].flags |= 4; } add_place_places (dpy, "Home", getenv ("HOME")); if (getenv ("HOME")) { strcpy (tmp, getenv ("HOME")); strcat (tmp, "/Desktop"); add_place_places (dpy, "Desktop", tmp); } add_place_places (dpy, "Filesystem", "/"); if (_placecnt > 0) spacer = _placecnt -1; if (strlen (_fib_cfg_custom_places) > 0) { parse_gtk_bookmarks (dpy, _fib_cfg_custom_places); } if (read_mtab (dpy, "/proc/mounts") < 1) { read_mtab (dpy, "/etc/mtab"); } int parsed_bookmarks = 0; if (!parsed_bookmarks && getenv ("HOME")) { strcpy (tmp, getenv ("HOME")); strcat (tmp, "/.gtk-bookmarks"); if (parse_gtk_bookmarks (dpy, tmp) > 0) { parsed_bookmarks = 1; } } if (!parsed_bookmarks && getenv ("XDG_CONFIG_HOME")) { strcpy (tmp, getenv ("XDG_CONFIG_HOME")); strcat (tmp, "/gtk-3.0/bookmarks"); if (parse_gtk_bookmarks (dpy, tmp) > 0) { parsed_bookmarks = 1; } } if (!parsed_bookmarks && getenv ("HOME")) { strcpy (tmp, getenv ("HOME")); strcat (tmp, "/.config/gtk-3.0/bookmarks"); if (parse_gtk_bookmarks (dpy, tmp) > 0) { parsed_bookmarks = 1; } } if (_fib_place_width > 0) { _fib_place_width = MIN (_fib_place_width + TEXTSEP + _fib_dir_indent /*extra*/ , PLACESWMAX); } if (spacer > 0 && spacer < _placecnt -1) { _placelist[ spacer ].flags |= 4; } } static uint8_t font_err = 0; static int x_error_handler (Display *d, XErrorEvent *e) { font_err = 1; return 0; } int x_fib_show (Display *dpy, Window parent, int x, int y) { if (_fib_win) { XSetInputFocus (dpy, _fib_win, RevertToParent, CurrentTime); return -1; } _status = 0; _rv_open[0] = '\0'; Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); _c_gray1.flags= DoRed | DoGreen | DoBlue; _c_gray0.red = _c_gray0.green = _c_gray0.blue = 61710; // 95% hover prelight _c_gray1.red = _c_gray1.green = _c_gray1.blue = 60416; // 93% window bg, scrollbar-fg _c_gray2.red = _c_gray2.green = _c_gray2.blue = 54016; // 83% button & list bg _c_gray3.red = _c_gray3.green = _c_gray3.blue = 48640; // 75% heading + scrollbar-bg _c_gray4.red = _c_gray4.green = _c_gray4.blue = 26112; // 40% prelight text, sep lines _c_gray5.red = _c_gray5.green = _c_gray5.blue = 12800; // 20% 3D border _c_gray6.red = _c_gray6.green = _c_gray6.blue = 6400; // 10% checkbox cross, sort triangles if (!XAllocColor (dpy, colormap, &_c_gray0)) return -1; if (!XAllocColor (dpy, colormap, &_c_gray1)) return -1; if (!XAllocColor (dpy, colormap, &_c_gray2)) return -1; if (!XAllocColor (dpy, colormap, &_c_gray3)) return -1; if (!XAllocColor (dpy, colormap, &_c_gray4)) return -1; if (!XAllocColor (dpy, colormap, &_c_gray5)) return -1; if (!XAllocColor (dpy, colormap, &_c_gray6)) return -1; XSetWindowAttributes attr; memset (&attr, 0, sizeof(XSetWindowAttributes)); attr.border_pixel = _c_gray2.pixel; attr.event_mask = ExposureMask | KeyPressMask | ButtonPressMask | ButtonReleaseMask | ConfigureNotify | StructureNotifyMask | PointerMotionMask | LeaveWindowMask; _fib_win = XCreateWindow ( dpy, DefaultRootWindow (dpy), x, y, _fib_width, _fib_height, 1, CopyFromParent, InputOutput, CopyFromParent, CWEventMask | CWBorderPixel, &attr); if (!_fib_win) { return 1; } if (parent) XSetTransientForHint (dpy, _fib_win, parent); XStoreName (dpy, _fib_win, "Select File"); Atom wmDelete = XInternAtom (dpy, "WM_DELETE_WINDOW", True); XSetWMProtocols (dpy, _fib_win, &wmDelete, 1); _fib_gc = XCreateGC (dpy, _fib_win, 0, NULL); XSetLineAttributes (dpy, _fib_gc, 1, LineSolid, CapButt, JoinMiter); const char dl[1] = {1}; XSetDashes (dpy, _fib_gc, 0, dl, 1); int (*handler)(Display *, XErrorEvent *) = XSetErrorHandler (&x_error_handler); #define _XTESTFONT(FN) \ { \ font_err = 0; \ _fibfont = XLoadFont (dpy, FN); \ XSetFont (dpy, _fib_gc, _fibfont); \ XSync (dpy, False); \ } font_err = 1; if (getenv ("XJFONT")) _XTESTFONT (getenv ("XJFONT")); if (font_err && strlen (_fib_cfg_custom_font) > 0) _XTESTFONT (_fib_cfg_custom_font); if (font_err) _XTESTFONT ("-*-helvetica-medium-r-normal-*-12-*-*-*-*-*-*-*"); if (font_err) _XTESTFONT ("-*-verdana-medium-r-normal-*-12-*-*-*-*-*-*-*"); if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-13-*-*-*-*-*-*-*"); if (font_err) _XTESTFONT ("-misc-fixed-medium-r-normal-*-12-*-*-*-*-*-*-*"); if (font_err) _fibfont = None; XSync (dpy, False); XSetErrorHandler (handler); if (_fib_font_height == 0) { // 1st time only query_font_geometry (dpy, _fib_gc, "D ", &_fib_dir_indent, NULL, NULL, NULL); query_font_geometry (dpy, _fib_gc, "_", &_fib_spc_norm, NULL, NULL, NULL); if (query_font_geometry (dpy, _fib_gc, "|0Yy", NULL, &_fib_font_height, &_fib_font_ascent, NULL)) { XFreeGC (dpy, _fib_gc); XDestroyWindow (dpy, _fib_win); _fib_win = 0; return -1; } _fib_font_height += 3; _fib_font_ascent += 2; _fib_font_vsep = _fib_font_height + 2; } populate_places (dpy); strcpy (_btn_ok.text, "Open"); strcpy (_btn_cancel.text, "Cancel"); strcpy (_btn_filter.text, "List All Files"); strcpy (_btn_places.text, "Show Places"); strcpy (_btn_hidden.text, "Show Hidden"); _btn_ok.callback = &cb_open; _btn_cancel.callback = &cb_cancel; _btn_filter.callback = &cb_filter; _btn_places.callback = &cb_places; _btn_hidden.callback = &cb_hidden; _btn_filter.flags |= 4; _btn_places.flags |= 4; _btn_hidden.flags |= 4; if (!_fib_filter_function) { _btn_filter.flags |= 8; } size_t i; int btncnt = 0; _btn_w = 0; _btn_span = 0; for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { if (_btns[i]->flags & 8) { continue; } query_font_geometry (dpy, _fib_gc, _btns[i]->text, &_btns[i]->tw, NULL, NULL, NULL); if (_btns[i]->flags & 4) { _btn_span += _btns[i]->tw + _fib_font_ascent + TEXTSEP; } else { ++btncnt; if (_btns[i]->tw > _btn_w) _btn_w = _btns[i]->tw; } } _btn_w += BTNPADDING + BTNPADDING + TEXTSEP + TEXTSEP + TEXTSEP; _btn_span += _btn_w * btncnt + DSEP * (i - 1) + FAREAMRGR + FAREAMRGB; for (i = 0; i < sizeof(_btns) / sizeof(FibButton*); ++i) { if (_btns[i]->flags & 8) { continue; } if (_btns[i]->flags & 4) { _btns[i]->xw = _btns[i]->tw + _fib_font_ascent + TEXTSEP; } else { _btns[i]->xw = _btn_w; } } sync_button_states () ; _fib_height = _fib_font_vsep * (15.8); _fib_width = MAX (_btn_span, 440); XResizeWindow (dpy, _fib_win, _fib_width, _fib_height); XTextProperty x_wname, x_iname; XSizeHints hints; XWMHints wmhints; hints.flags = PSize | PMinSize; hints.min_width = _btn_span; hints.min_height = 8 * _fib_font_vsep; char *w_name = & _fib_cfg_title[0]; wmhints.input = True; wmhints.flags = InputHint; if (XStringListToTextProperty (&w_name, 1, &x_wname) && XStringListToTextProperty (&w_name, 1, &x_iname)) { XSetWMProperties (dpy, _fib_win, &x_wname, &x_iname, NULL, 0, &hints, &wmhints, NULL); XFree (x_wname.value); XFree (x_iname.value); } XSetWindowBackground (dpy, _fib_win, _c_gray1.pixel); _fib_mapped = 0; XMapRaised (dpy, _fib_win); if (!strlen (_cur_path) || !fib_opendir (dpy, _cur_path, NULL)) { fib_opendir (dpy, getenv ("HOME") ? getenv ("HOME") : "/", NULL); } #if 0 XGrabPointer (dpy, _fib_win, True, ButtonReleaseMask | ButtonPressMask | EnterWindowMask | LeaveWindowMask | PointerMotionMask | StructureNotifyMask, GrabModeAsync, GrabModeAsync, None, None, CurrentTime); XGrabKeyboard (dpy, _fib_win, True, GrabModeAsync, GrabModeAsync, CurrentTime); //XSetInputFocus (dpy, parent, RevertToNone, CurrentTime); #endif _recentlock = 1; return 0; } void x_fib_close (Display *dpy) { if (!_fib_win) return; XFreeGC (dpy, _fib_gc); XDestroyWindow (dpy, _fib_win); _fib_win = 0; free (_dirlist); _dirlist = NULL; free (_pathbtn); _pathbtn = NULL; if (_fibfont != None) XUnloadFont (dpy, _fibfont); _fibfont = None; free (_placelist); _placelist = NULL; _dircount = 0; _pathparts = 0; _placecnt = 0; if (_pixbuffer != None) XFreePixmap (dpy, _pixbuffer); _pixbuffer = None; Colormap colormap = DefaultColormap (dpy, DefaultScreen (dpy)); XFreeColors (dpy, colormap, &_c_gray0.pixel, 1, 0); XFreeColors (dpy, colormap, &_c_gray1.pixel, 1, 0); XFreeColors (dpy, colormap, &_c_gray2.pixel, 1, 0); XFreeColors (dpy, colormap, &_c_gray3.pixel, 1, 0); XFreeColors (dpy, colormap, &_c_gray4.pixel, 1, 0); XFreeColors (dpy, colormap, &_c_gray5.pixel, 1, 0); XFreeColors (dpy, colormap, &_c_gray6.pixel, 1, 0); _recentlock = 0; } int x_fib_handle_events (Display *dpy, XEvent *event) { if (!_fib_win) return 0; if (_status) return 0; if (event->xany.window != _fib_win) { return 0; } switch (event->type) { case MapNotify: _fib_mapped = 1; break; case UnmapNotify: _fib_mapped = 0; break; case LeaveNotify: fib_update_hover (dpy, 1, 0, 0); break; case ClientMessage: if (!strcmp (XGetAtomName (dpy, event->xclient.message_type), "WM_PROTOCOLS")) { _status = -1; } case ConfigureNotify: if ( (event->xconfigure.width > 1 && event->xconfigure.height > 1) && (event->xconfigure.width != _fib_width || event->xconfigure.height != _fib_height) ) { _fib_width = event->xconfigure.width; _fib_height = event->xconfigure.height; _fib_resized = 1; } break; case Expose: if (event->xexpose.count == 0) { fib_expose (dpy, event->xany.window); } break; case MotionNotify: fib_motion (dpy, event->xmotion.x, event->xmotion.y); if (event->xmotion.is_hint == NotifyHint) { XGetMotionEvents (dpy, event->xany.window, CurrentTime, CurrentTime, NULL); } break; case ButtonPress: fib_mousedown (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); break; case ButtonRelease: fib_mouseup (dpy, event->xbutton.x, event->xbutton.y, event->xbutton.button, event->xbutton.time); break; case KeyRelease: break; case KeyPress: { KeySym key; char buf[100]; static XComposeStatus stat; XLookupString (&event->xkey, buf, sizeof(buf), &key, &stat); switch (key) { case XK_Escape: _status = -1; break; case XK_Up: if (_fsel > 0) { fib_select (dpy, _fsel - 1); } break; case XK_Down: if (_fsel < _dircount -1) { fib_select ( dpy, _fsel + 1); } break; case XK_Page_Up: if (_fsel > 0) { int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; if (llen < 1) llen = 1; else --llen; int fs = MAX (0, _fsel - llen); fib_select ( dpy, fs); } break; case XK_Page_Down: if (_fsel < _dircount) { int llen = (_fib_height - LISTBOT * _fib_font_vsep) / _fib_font_vsep; if (llen < 1) llen = 1; else --llen; int fs = MIN (_dircount - 1, _fsel + llen); fib_select ( dpy, fs); } break; case XK_Left: if (_pathparts > 1) { int i = 0; char path[1024] = "/"; while (++i < _pathparts - 1) { strcat (path, _pathbtn[i].name); strcat (path, "/"); } char *sel = strdup (_pathbtn[_pathparts-1].name); fib_opendir (dpy, path, sel); free (sel); } break; case XK_Right: if (_fsel >= 0 && _fsel < _dircount) { if (_dirlist[_fsel].flags & 4) { cb_open (dpy); } } break; case XK_Return: cb_open (dpy); break; default: if ((key >= XK_a && key <= XK_z) || (key >= XK_0 && key <= XK_9)) { int i; for (i = 0; i < _dircount; ++i) { int j = (_fsel + i + 1) % _dircount; char kcmp = _dirlist[j].name[0]; if (kcmp > 0x40 && kcmp <= 0x5A) kcmp |= 0x20; if (kcmp == (char)key) { fib_select ( dpy, j); break; } } } break; } } break; } if (_status) { x_fib_close (dpy); } return _status; } int x_fib_status () { return _status; } int x_fib_configure (int k, const char *v) { if (_fib_win) { return -1; } switch (k) { case 0: if (strlen (v) >= sizeof(_cur_path) -1) return -2; if (strlen (v) < 1) return -2; if (v[0] != '/') return -2; if (strstr (v, "//")) return -2; strncpy (_cur_path, v, sizeof(_cur_path)); break; case 1: if (strlen (v) >= sizeof(_fib_cfg_title) -1) return -2; strncpy (_fib_cfg_title, v, sizeof(_fib_cfg_title)); break; case 2: if (strlen (v) >= sizeof(_fib_cfg_custom_font) -1) return -2; strncpy (_fib_cfg_custom_font, v, sizeof(_fib_cfg_custom_font)); break; case 3: if (strlen (v) >= sizeof(_fib_cfg_custom_places) -1) return -2; strncpy (_fib_cfg_custom_places, v, sizeof(_fib_cfg_custom_places)); break; default: return -2; } return 0; } int x_fib_cfg_buttons (int k, int v) { if (_fib_win) { return -1; } switch (k) { case 1: if (v < 0) { _btn_hidden.flags |= 8; } else { _btn_hidden.flags &= ~8; } if (v == 1) { _btn_hidden.flags |= 2; _fib_hidden_fn = 1; } else if (v == 0) { _btn_hidden.flags &= 2; _fib_hidden_fn = 0; } break; case 2: if (v < 0) { _btn_places.flags |= 8; } else { _btn_places.flags &= ~8; } if (v == 1) { _btn_places.flags |= 2; _fib_show_places = 1; } else if (v == 0) { _btn_places.flags &= ~2; _fib_show_places = 0; } break; case 3: // NB. filter button is automatically hidden // IFF the filter-function is NULL. if (v < 0) { _btn_filter.flags |= 8; } else { _btn_filter.flags &= ~8; } if (v == 1) { _btn_filter.flags &= ~2; // inverse - 'show all' = !filter _fib_filter_fn = 1; } else if (v == 0) { _btn_filter.flags |= 2; _fib_filter_fn = 0; } default: return -2; } return 0; } int x_fib_cfg_filter_callback (int (*cb)(const char*)) { if (_fib_win) { return -1; } _fib_filter_function = cb; return 0; } char *x_fib_filename () { if (_status > 0 && !_fib_win) return strdup (_rv_open); else return NULL; } #endif // HAVE_X11 /* example usage */ #ifdef SOFD_TEST static int fib_filter_movie_filename (const char *name) { if (!_fib_filter_fn) return 1; const int l3 = strlen (name) - 3; const int l4 = l3 - 1; const int l5 = l4 - 1; const int l6 = l5 - 1; const int l9 = l6 - 3; if ( (l4 > 0 && ( !strcasecmp (&name[l4], ".avi") || !strcasecmp (&name[l4], ".mov") || !strcasecmp (&name[l4], ".ogg") || !strcasecmp (&name[l4], ".ogv") || !strcasecmp (&name[l4], ".mpg") || !strcasecmp (&name[l4], ".mov") || !strcasecmp (&name[l4], ".mp4") || !strcasecmp (&name[l4], ".mkv") || !strcasecmp (&name[l4], ".vob") || !strcasecmp (&name[l4], ".asf") || !strcasecmp (&name[l4], ".avs") || !strcasecmp (&name[l4], ".dts") || !strcasecmp (&name[l4], ".flv") || !strcasecmp (&name[l4], ".m4v") )) || (l5 > 0 && ( !strcasecmp (&name[l5], ".h264") || !strcasecmp (&name[l5], ".webm") )) || (l6 > 0 && ( !strcasecmp (&name[l6], ".dirac") )) || (l9 > 0 && ( !strcasecmp (&name[l9], ".matroska") )) || (l3 > 0 && ( !strcasecmp (&name[l3], ".dv") || !strcasecmp (&name[l3], ".ts") )) ) { return 1; } return 0; } int main (int argc, char **argv) { Display* dpy = XOpenDisplay (0); if (!dpy) return -1; x_fib_cfg_filter_callback (fib_filter_movie_filename); x_fib_configure (1, "Open Movie File"); x_fib_load_recent ("/tmp/sofd.recent"); x_fib_show (dpy, 0, 300, 300); while (1) { XEvent event; while (XPending (dpy) > 0) { XNextEvent (dpy, &event); if (x_fib_handle_events (dpy, &event)) { if (x_fib_status () > 0) { char *fn = x_fib_filename (); printf ("OPEN '%s'\n", fn); x_fib_add_recent (fn, time (NULL)); free (fn); } } } if (x_fib_status ()) { break; } usleep (80000); } x_fib_close (dpy); x_fib_save_recent ("/tmp/sofd.recent"); x_fib_free_recent (); XCloseDisplay (dpy); return 0; } #endif robtk-0.8.5/sofd/libsofd.h000066400000000000000000000135511463170413500154040ustar00rootroot00000000000000/* libSOFD - Simple Open File Dialog [for X11 without toolkit] * * Copyright (C) 2014 Robin Gareus * * Permission is hereby granted, free of charge, to any person obtaining a copy * of this software and associated documentation files (the "Software"), to deal * in the Software without restriction, including without limitation the rights * to use, copy, modify, merge, publish, distribute, sublicense, and/or sell * copies of the Software, and to permit persons to whom the Software is * furnished to do so, subject to the following conditions: * * The above copyright notice and this permission notice shall be included in * all copies or substantial portions of the Software. * * THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR * IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, * FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE * AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER * LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, * OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN * THE SOFTWARE. */ #ifdef HAVE_X11 #include /////////////////////////////////////////////////////////////////////////////// /* public API */ /** open a file select dialog * @param dpy X Display connection * @param parent (optional) if not NULL, become transient for given window * @param x if >0 set explict initial width of the window * @param y if >0 set explict initial height of the window * @return 0 on success */ int x_fib_show (Display *dpy, Window parent, int x, int y); /** force close the dialog. * This is normally not needed, the dialog closes itself * when a file is selected or the user cancels selection. * @param dpy X Display connection */ void x_fib_close (Display *dpy); /** non-blocking X11 event handler. * It is safe to run this function even if the dialog is * closed or was not initialized. * * @param dpy X Display connection * @param event the XEvent to process * @return status * 0: the event was not for this window, or file-dialog still * active, or the dialog window is not displayed. * >0: file was selected, dialog closed * <0: file selection was cancelled. */ int x_fib_handle_events (Display *dpy, XEvent *event); /** last status of the dialog * @return >0: file was selected, <0: canceled or inactive. 0: active */ int x_fib_status (); /** query the selected filename * @return NULL if none set, or allocated string to be free()ed by the called */ char *x_fib_filename (); /** customize/configure the dialog before calling \ref x_fib_show * changes only have any effect if the dialog is not visible. * @param k key to change * 0: set current dir to display (must end with slash) * 1: set title of dialog window * 2: specify a custom X11 font to use * 3: specify a custom 'places' file to include * (following gtk-bookmark convention) * @param v value * @return 0 on success. */ int x_fib_configure (int k, const char *v); /** customize/configure the dialog before calling \ref x_fib_show * changes only have any effect if the dialog is not visible. * * @param k button to change: * 1: show hidden files * 2: show places * 3: show filter/list all (automatically hidden if there is no * filter function) * @param v <0 to hide the button >=0 show button, * 0: set button-state to not-checked * 1: set button-state to checked * >1: retain current state * @return 0 on success. */ int x_fib_cfg_buttons (int k, int v); /** set custom callback to filter file-names. * NULL will disable the filter and hide the 'show all' button. * changes only have any effect if the dialog is not visible. * * @param cb callback function to check file * the callback function is called with the file name (basename only) * and is expected to return 1 if the file passes the filter * and 0 if the file should not be listed by default. * @return 0 on success. */ int x_fib_cfg_filter_callback (int (*cb)(const char*)); #endif /* END X11 specific functions */ /* 'recently used' API. x-platform * NOTE: all functions use a static cache and are not reentrant. * It is expected that none of these functions are called in * parallel from different threads. */ /** release static resources of 'recently used files' */ void x_fib_free_recent (); /** add an entry to the recently used list * * The dialog does not add files automatically on open, * if the application succeeds to open a selected file, * this function should be called. * * @param path complete path to file * @param atime time of last use, 0: NOW * @return -1 on error, number of current entries otherwise */ int x_fib_add_recent (const char *path, time_t atime); /** get a platform specific path to a good location for * saving the recently used file list. * (follows XDG_DATA_HOME on Unix, and CSIDL_LOCAL_APPDATA spec) * * @param application-name to use to include in file * @return pointer to static path or NULL */ const char *x_fib_recent_file(const char *appname); /** save the current list of recently used files to the given filename * (the format is one file per line, filename URL encoded and space separated * with last-used timestamp) * * This function tries to creates the containing directory if it does * not exist. * * @param fn file to save the list to * @return 0: on success */ int x_fib_save_recent (const char *fn); /** load a recently used file list. * * @param fn file to load the list from * @return 0: on success */ int x_fib_load_recent (const char *fn); /** get number of entries in the current list * @return number of entries in the recently used list */ unsigned int x_fib_recent_count (); /** get recently used entry at given position * * @param i entry to query * @return pointer to static string */ const char *x_fib_recent_at (unsigned int i); robtk-0.8.5/ui_gl.c000066400000000000000000001431221463170413500141170ustar00rootroot00000000000000/* meters.lv2 openGL frontend * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ //#undef HAVE_IDLE_IFACE // simulate old LV2 //#define USE_GUI_THREAD // use own thread (not idle callback), should be preferred even w/idle availale on most platforms //#define THREADSYNC // wake up GUI thread on port-event #ifdef XTERNAL_UI #if defined USE_GUI_THREAD && defined _WIN32 # define INIT_PUGL_IN_THREAD #endif #endif //#define TIMED_RESHAPE // resize view when idle //#define DEBUG_RESIZE //#define DEBUG_EXPOSURE //#define VISIBLE_EXPOSE //#define DEBUG_UI /* either USE_GUI_THREAD or HAVE_IDLE_IFACE needs to be defined. * * if both are defined: * the goniometer window can change size by itself.. * * IFF HAVE_IDLE_IFACE is available we can use it to * resize the suil-swallowed window (gtk host only), by * making a call to gtk_window_resize of the gtk_widget_get_toplevel * in the thread-context of the main application. * * without USE_GUI_THREAD but with HAVE_IDLE_IFACE: * All plugins run single threaded in the host's process context. * * That will avoid various threading issues - particularly with * libcairo < 1.12.10, libpixman < 0.30.2 and libpango < 1.32.6 * which are not threadsafe) * * The plugin UI launching its own thread yields generally better * performance (the host's idle call's timing is inaccurate and * using the idle interface will also slow down the host's UI... */ #if (!defined HAVE_IDLE_IFACE && !defined USE_GUI_THREAD) #error At least one of HAVE_IDLE_IFACE or USE_GUI_THREAD must be defined. #endif #define _POSIX_C_SOURCE 200809L #include #include #include #include #include #include #include #include "pugl/pugl.h" #ifdef __APPLE__ #include "OpenGL/glu.h" #else #include #endif #ifndef GL_BGRA #define GL_BGRA 0x80E1 // GL_BGRA_EXT #endif #ifndef GL_RGBA8 #define GL_RGBA8 GL_RGBA #endif #ifndef GL_TEXTURE_RECTANGLE_ARB #define GL_TEXTURE_RECTANGLE_ARB 0x84F5 #endif #ifdef USE_GTK_RESIZE_HACK #include #endif #ifndef M_PI #define M_PI 3.14159265358979323846 #endif #define ROBTK_MOD_SHIFT PUGL_MOD_SHIFT #define ROBTK_MOD_CTRL PUGL_MOD_CTRL #include "gl/posringbuf.h" #include "robtk.h" #ifdef WITH_SIGNATURE # include "gpg_init.c" #endif static void opengl_init () { glClearColor (0.0f, 0.0f, 0.0f, 0.0f); glDisable (GL_DEPTH_TEST); glEnable (GL_BLEND); glBlendFunc (GL_SRC_ALPHA, GL_ONE_MINUS_SRC_ALPHA); glEnable (GL_TEXTURE_RECTANGLE_ARB); } static void opengl_draw (int width, int height, unsigned char* surf_data, unsigned int texture_id) { if (!surf_data) { return; } glMatrixMode(GL_MODELVIEW); glLoadIdentity(); glClear(GL_COLOR_BUFFER_BIT); glPushMatrix (); glEnable(GL_TEXTURE_2D); glBindTexture(GL_TEXTURE_RECTANGLE_ARB, texture_id); glTexImage2D(GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, width, height, /*border*/ 0, GL_BGRA, GL_UNSIGNED_BYTE, surf_data); glBegin(GL_QUADS); glTexCoord2f( 0.0f, (GLfloat) height); glVertex2f(-1.0f, -1.0f); glTexCoord2f((GLfloat) width, (GLfloat) height); glVertex2f( 1.0f, -1.0f); glTexCoord2f((GLfloat) width, 0.0f); glVertex2f( 1.0f, 1.0f); glTexCoord2f( 0.0f, 0.0f); glVertex2f(-1.0f, 1.0f); glEnd(); glDisable(GL_TEXTURE_2D); glPopMatrix(); } static void opengl_reallocate_texture (int width, int height, unsigned int* texture_id) { glViewport (0, 0, width, height); glMatrixMode (GL_PROJECTION); glLoadIdentity (); glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); glClear (GL_COLOR_BUFFER_BIT); glDeleteTextures (1, texture_id); glGenTextures (1, texture_id); glBindTexture (GL_TEXTURE_RECTANGLE_ARB, *texture_id); glTexImage2D (GL_TEXTURE_RECTANGLE_ARB, 0, GL_RGBA8, width, height, 0, GL_BGRA, GL_UNSIGNED_BYTE, NULL); glTexEnvi (GL_TEXTURE_ENV, GL_TEXTURE_ENV_MODE, GL_DECAL); } static cairo_t* opengl_create_cairo_t (int width, int height, cairo_surface_t** surface, unsigned char** buffer) { cairo_t* cr; const int bpp = 4; *buffer = (unsigned char*) calloc (bpp * width * height, sizeof (unsigned char)); if (!*buffer) { fprintf (stderr, "robtk: opengl surface out of memory.\n"); return NULL; } *surface = cairo_image_surface_create_for_data (*buffer, CAIRO_FORMAT_ARGB32, width, height, bpp * width); if (cairo_surface_status (*surface) != CAIRO_STATUS_SUCCESS) { free (*buffer); fprintf (stderr, "robtk: failed to create cairo surface\n"); return NULL; } cr = cairo_create (*surface); if (cairo_status (cr) != CAIRO_STATUS_SUCCESS) { free (*buffer); fprintf (stderr, "robtk: cannot create cairo context\n"); return NULL; } return cr; } /*****************************************************************************/ #include "gl/xternalui.h" typedef struct { PuglView* view; LV2UI_Resize* resize; LV2UI_Write_Function write; // unused for now LV2UI_Controller controller; // unused for now PuglNativeWindow parent; // only valud during init bool ontop; unsigned long transient_id; // X11 window ID struct lv2_external_ui_host *extui; struct lv2_external_ui xternal_ui; int width; int height; int xoff; int yoff; float xyscale; bool gl_initialized; #ifdef WITH_SIGNATURE bool gpg_verified; char gpg_data[128]; float gpg_shade; #endif #ifdef INIT_PUGL_IN_THREAD int ui_initialized; #endif bool resize_in_progress; bool resize_toplevel; #ifdef USE_GUI_THREAD int ui_queue_puglXWindow; pthread_t thread; int exit; #endif #ifdef TIMED_RESHAPE uint64_t queue_reshape; int queue_w; int queue_h; #else bool relayout; #endif cairo_t* cr; cairo_surface_t* surface; unsigned char* surf_data; #if __BIG_ENDIAN__ unsigned char* surf_data_be; #endif unsigned int texture_id; /* top-level */ RobWidget *tl; LV2UI_Handle ui; /* toolkit state stuff */ volatile cairo_rectangle_t expose_area; RobWidget *mousefocus; RobWidget *mousehover; posringbuf *rb; #if (defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE) bool do_the_funky_resize; #endif bool queue_canvas_realloc; void (* ui_closed)(void* controller); bool close_ui; // used by xternalui #if (defined USE_GUI_THREAD && defined THREADSYNC) pthread_mutex_t msg_thread_lock; pthread_cond_t data_ready; #endif void (* expose_overlay)(RobWidget* toplevel, cairo_t* cr, cairo_rectangle_t *ev); void (* scale_change_cb)(RobWidget* toplevel, void* handle); float queue_widget_scale; void* scale_change_handle; } GLrobtkLV2UI; static const char * robtk_info(void *h) { #ifdef WITH_SIGNATURE GLrobtkLV2UI * self = (GLrobtkLV2UI*) h; return self->gpg_data; #else return "v" VERSION; #endif } static void robtk_close_self(void *h) { GLrobtkLV2UI * self = (GLrobtkLV2UI*) h; if (self->ui_closed) { self->ui_closed(self); } } static int robtk_open_file_dialog(void *h, const char *title) { GLrobtkLV2UI * self = (GLrobtkLV2UI*) h; return puglOpenFileDialog(self->view, title); } /*****************************************************************************/ #include PLUGIN_SOURCE /*****************************************************************************/ #ifdef WITH_SIGNATURE #include WITH_SIGNATURE #endif #ifdef ROBTKAPP static const void* extension_data(const char* uri) { return NULL; } static void port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void* buffer) { } #endif /*****************************************************************************/ #include "gl/xternalui.c" static void reallocate_canvas(GLrobtkLV2UI* self); /*****************************************************************************/ /* RobWidget implementation & glue */ typedef struct { RobWidget *rw; cairo_rectangle_t a; } RWArea; #ifdef WITH_SIGNATURE static void lc_expose (GLrobtkLV2UI * self) { #ifdef ROBTK_UPSCALE #if __BIG_ENDIAN__ float hw_scale = 1.0; #else float hw_scale = puglGetHWSurfaceScale(self->view); #endif #endif assert (self->tl); if (!self->tl) { return; } cairo_rectangle_t expose_area; posrb_read_clear(self->rb); // no fast-track expose_area.x = expose_area.y = 0; expose_area.width = self->width; expose_area.height = self->height; cairo_save(self->cr); #ifdef ROBTK_UPSCALE cairo_scale(self->cr, hw_scale, hw_scale); #endif self->tl->resized = TRUE; // full re-expose self->tl->expose_event(self->tl, self->cr, &expose_area); cairo_restore(self->cr); expose_area.x = expose_area.y = 0; expose_area.width = self->width; expose_area.height = self->height; PangoFontDescription *xfont = pango_font_description_from_string("Sans 16px"); cairo_save(self->cr); #ifdef ROBTK_UPSCALE cairo_scale(self->cr, hw_scale, hw_scale); #endif cairo_rectangle (self->cr, 0, 0, self->width, self->height); cairo_set_operator (self->cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(self->cr, 0, 0, 0, .15 + self->gpg_shade); if (self->gpg_shade < .6) self->gpg_shade += .001; cairo_fill(self->cr); write_text_full(self->cr, "Unregistered Version\nhttp://x42-plugins.com", xfont, self->width * .5, self->height * .5, self->width < 200 ? M_PI * -.5 : 0, -2, c_wht); pango_font_description_free(xfont); cairo_restore(self->cr); cairo_surface_mark_dirty(self->surface); } #endif static void cairo_expose(GLrobtkLV2UI * self) { #ifdef ROBTK_UPSCALE #if __BIG_ENDIAN__ float hw_scale = 1.0; #else float hw_scale = puglGetHWSurfaceScale(self->view); #endif #endif if (self->expose_overlay) { cairo_rectangle_t expose_area; posrb_read_clear(self->rb); // no fast-track self->tl->resized = TRUE; // full re-expose expose_area.x = expose_area.y = 0; expose_area.width = self->width; expose_area.height = self->height; cairo_save(self->cr); #ifdef ROBTK_UPSCALE cairo_scale(self->cr, hw_scale, hw_scale); #endif self->tl->expose_event(self->tl, self->cr, &expose_area); cairo_restore(self->cr); cairo_save(self->cr); #ifdef ROBTK_UPSCALE cairo_scale(self->cr, hw_scale, hw_scale); #endif self->expose_overlay (self->tl, self->cr, &expose_area); cairo_restore(self->cr); return; } /* FAST TRACK EXPOSE */ int qq = posrb_read_space(self->rb) / sizeof(RWArea); bool dirty = qq > 0; #ifdef DEBUG_FASTTRACK /*if (qq > 0)*/ fprintf(stderr, " fast track %d events\n", qq); #endif cairo_rectangle_t fast_track_area = {0,0,0,0}; uint32_t fast_track_cnt = 0; while (--qq >= 0) { RWArea a; posrb_read(self->rb, (uint8_t*) &a, sizeof(RWArea)); assert(a.rw); /* skip duplicates */ if (fast_track_cnt > 0) { if ( a.a.x+a.rw->trel.x >= fast_track_area.x && a.a.y+a.rw->trel.y >= fast_track_area.y && a.a.x+a.rw->trel.x+a.a.width <= fast_track_area.x + fast_track_area.width && a.a.y+a.rw->trel.y+a.a.height <= fast_track_area.y + fast_track_area.height) { #ifdef DEBUG_FASTTRACK fprintf(stderr, " skip fast-track event #%d (%.1f x %.1f + %.1f + %.1f)\n", fast_track_cnt, a.a.width, a.a.height, a.a.x+a.rw->trel.x, a.a.y+a.rw->trel.y); #endif continue; } } cairo_save(self->cr); #ifdef ROBTK_UPSCALE cairo_scale(self->cr, hw_scale, hw_scale); #endif cairo_translate(self->cr, a.rw->trel.x, a.rw->trel.y); a.rw->expose_event(a.rw, self->cr, &a.a); /* keep track of exposed parts */ a.a.x += a.rw->trel.x; a.a.y += a.rw->trel.y; #ifdef DEBUG_FASTTRACK fprintf(stderr, " #%d (%.1f x %.1f @ %.1f + %.1f\n", fast_track_cnt, a.a.width, a.a.height, a.a.x, a.a.y); #endif #ifndef FASTTRACK_INTERSECT memcpy(&fast_track_area, &a.a, sizeof(cairo_rectangle_t)); fast_track_cnt++; #else // intersects if (fast_track_cnt++ == 0) { fast_track_area.x = a.a.x; fast_track_area.y = a.a.y; fast_track_area.width = a.a.width; fast_track_area.height = a.a.height; } else { rect_intersection(&fast_track_area, &a.a, &fast_track_area); //rect_combine(&fast_track_area, &a.a, &fast_track_area); } #endif #ifdef VISIBLE_EXPOSE static int ftrk = 0; static int fcol = 0; ftrk = (ftrk + 1) %11; fcol = (fcol + 1) %17; cairo_rectangle (self->cr, 0, 0, a.rw->trel.width, a.rw->trel.height); cairo_set_operator (self->cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(self->cr, .8, .5 + ftrk/25.0, .5 - fcol/40.0, .25 + ftrk/30.0); cairo_fill(self->cr); #endif cairo_restore(self->cr); } if (self->expose_area.width == 0 || self->expose_area.height == 0) { #ifdef DEBUG_EXPOSURE fprintf(stderr, " --- NO DRAW\n"); #endif if (dirty) { cairo_surface_mark_dirty(self->surface); } return; } #ifdef DEBUG_EXPOSURE fprintf(stderr, "XPS %.1f+%.1f %.1fx%.1f\n", self->expose_area.x, self->expose_area.y, self->expose_area.width, self->expose_area.height); #endif /* make copy and clear -- should be an atomic op when using own thread */ cairo_rectangle_t area; area.x = self->expose_area.x; area.y = self->expose_area.y; area.width = self->expose_area.width; area.height = self->expose_area.height; self->expose_area.x = 0; self->expose_area.y = 0; self->expose_area.width = 0; self->expose_area.height = 0; #ifdef DEBUG_EXPOSURE fprintf(stderr, "---- XPS ---\n"); #endif // intersect exposure with toplevel cairo_rectangle_t expose_area; expose_area.x = MAX(0, area.x - self->tl->area.x); expose_area.y = MAX(0, area.y - self->tl->area.y); expose_area.width = MIN(area.x + area.width, self->tl->area.x + self->tl->area.width) - MAX(area.x, self->tl->area.x); expose_area.height = MIN(area.y + area.height, self->tl->area.y + self->tl->area.height) - MAX(area.y, self->tl->area.y); if (expose_area.width < 0 || expose_area.height < 0) { fprintf(stderr, " !!! EMPTY AREA\n"); return; } #define XPS_NO_DRAW { fprintf(stderr, " !!! OUTSIDE DRAW %.1fx%.1f %.1f+%.1f %.1fx%.1f\n", area.x, area.y, self->tl->area.x, self->tl->area.y, self->tl->area.width, self->tl->area.height); return; } #if 1 if (area.x > self->tl->area.x + self->tl->area.width) XPS_NO_DRAW if (area.y > self->tl->area.y + self->tl->area.height) XPS_NO_DRAW if (area.x < self->tl->area.x) XPS_NO_DRAW if (area.y < self->tl->area.y) XPS_NO_DRAW #endif cairo_save(self->cr); #ifdef ROBTK_UPSCALE cairo_scale(self->cr, hw_scale, hw_scale); #endif self->tl->expose_event(self->tl, self->cr, &expose_area); cairo_restore(self->cr); #ifdef VISIBLE_EXPOSE static int move = 0; static int hueh = 0; move = (move + 1) %10; hueh = (hueh + 1) %13; cairo_rectangle (self->cr, expose_area.x, expose_area.y, expose_area.width, expose_area.height); cairo_set_operator (self->cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba(self->cr, .7 + hueh / 50.0, .3, .5 - hueh/30, .25 + move/20.0); cairo_fill(self->cr); #endif cairo_surface_mark_dirty(self->surface); } static void queue_draw_full(RobWidget *rw) { GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); if (!self || !self->view) { rw->redraw_pending = true; return; } #ifdef DEBUG_EXPOSURE fprintf(stderr, "~~ queue_draw_full '%s'\n", ROBWIDGET_NAME(rw)); #endif self->expose_area.x = 0; self->expose_area.y = 0; self->expose_area.width = self->width; self->expose_area.height = self->height; puglPostRedisplay(self->view); } static void queue_draw_area(RobWidget *rw, int x, int y, int width, int height) { GLrobtkLV2UI * self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); if (!self || !self->view) { rw->redraw_pending = true; return; } if (x < 0) x = 0; if (y < 0) y = 0; if (x + width > rw->area.width) width = rw->area.width - x; if (y + height > rw->area.height) height = rw->area.height - y; if (self->expose_area.width == 0 || self->expose_area.height == 0) { RobTkBtnEvent ev; ev.x = x; ev.y = y; #ifdef DEBUG_EXPOSURE fprintf(stderr, "Q PARTIAL NEW '%s': ", ROBWIDGET_NAME(rw)); #endif offset_traverse_from_child(rw, &ev); #ifdef DEBUG_EXPOSURE fprintf(stderr, "Q PARTIAL -> %d+%d -> %d+%d (%dx%d)\n", x, y, ev.x, ev.y, width, height); #endif self->expose_area.x = ev.x; self->expose_area.y = ev.y; self->expose_area.width = width; self->expose_area.height = height; } else { RobTkBtnEvent ev; ev.x = x; ev.y = y; #ifdef DEBUG_EXPOSURE fprintf(stderr, "Q PARTIAL AMEND '%s': ", ROBWIDGET_NAME(rw)); #endif offset_traverse_from_child(rw, &ev); #ifdef DEBUG_EXPOSURE fprintf(stderr, "Q PARTIAL -> %d+%d -> %d+%d (%dx%d)\n", x, y, ev.x, ev.y, width, height); #endif cairo_rectangle_t r; r.x = ev.x; r.y = ev.y; r.width = width; r.height = height; rect_combine((cairo_rectangle_t*) &self->expose_area, &r, (cairo_rectangle_t*) &self->expose_area); } puglPostRedisplay(self->view); } static void queue_draw(RobWidget *rw) { #ifdef DEBUG_EXPOSURE fprintf(stderr, "FULL widget -> "); #endif queue_draw_area(rw, 0, 0, rw->area.width, rw->area.height); } static void queue_tiny_rect(RobWidget *rw, cairo_rectangle_t *a) { if (!rw->cached_position) { rw->redraw_pending = true; queue_draw(rw); return; } GLrobtkLV2UI * self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw);// XXX cache ?! if (!self || !self->view) { rw->redraw_pending = true; return; } RWArea b; b.rw = rw; memcpy(&b.a, a, sizeof(cairo_rectangle_t)); if (posrb_write(self->rb, (uint8_t*) &b, sizeof(RWArea))<0) { queue_draw_area(rw, a->x, a->y, a->width, a->height); } puglPostRedisplay(self->view); } static void queue_tiny_area(RobWidget *rw, float x, float y, float w, float h) { cairo_rectangle_t a; a.x = x; a.width = w; a.y = y - 1; a.height = h + 1; queue_tiny_rect(rw, &a); } static void robwidget_layout(GLrobtkLV2UI * const self, bool setsize, bool init) { RobWidget * rw = self->tl; #ifdef DEBUG_RESIZE printf("robwidget_layout(%s) setsize:%s init:%s\n", ROBWIDGET_NAME(self->tl), setsize ? "true" : "false", init ? "true" : "false" ); #endif int oldw = self->width; int oldh = self->height; bool size_changed = FALSE; int nox, noy; rtoplevel_scale (self->tl, self->tl->widget_scale); self->tl->size_request(self->tl, &nox, &noy); if (!init && rw->size_limit) { self->tl->size_limit(self->tl, &self->width, &self->height); if (oldw != self->width || oldh != self->height) { size_changed = TRUE; } } else if (setsize) { if (oldw != nox || oldh != noy) { size_changed = TRUE; } self->width = nox; self->height = noy; } else if (nox > self->width || noy > self->height) { enum LVGLResize rsz = plugin_scale_mode(self->ui); if (rsz == LVGL_ZOOM_TO_ASPECT || rsz == LVGL_LAYOUT_TO_FIT) { puglUpdateGeometryConstraints(self->view, nox, noy, rsz == LVGL_ZOOM_TO_ASPECT); return; } fprintf(stderr, "WINDOW IS SMALLER THAN MINIMUM SIZE! %d > %d h: %d > %d\n", nox, self->width, noy, self->height); if (nox > self->width) self->width = nox; if (noy > self->height) self->height = noy; } else if (nox < self->width || noy < self->height) { enum LVGLResize rsz = plugin_scale_mode(self->ui); if (rsz == LVGL_ZOOM_TO_ASPECT || rsz == LVGL_LAYOUT_TO_FIT) { puglUpdateGeometryConstraints(self->view, nox, noy, rsz == LVGL_ZOOM_TO_ASPECT); } } if (rw->size_allocate) { self->tl->size_allocate(rw, self->width, self->height); } rtoplevel_cache(rw, TRUE); if (init) { return; } if (setsize && size_changed) { self->resize_in_progress = TRUE; puglPostResize(self->view); } else { queue_draw_full(rw); } } // called by a widget if size changes static void resize_self(RobWidget *rw) { GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); if (!self || !self->view) { return; } #ifdef DEBUG_RESIZE printf("resize_self(%s)\n", ROBWIDGET_NAME(rw)); #endif robwidget_layout(self, TRUE, FALSE); } static void resize_toplevel(RobWidget *rw, int w, int h) { GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); if (!self || !self->view) { return; } self->width = w; self->height = h; resize_self(rw); self->resize_in_progress = TRUE; self->resize_toplevel = TRUE; puglPostResize(self->view); } static void relayout_toplevel(RobWidget *rw) { GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); if (!self || !self->view) { return; } #ifdef TIMED_RESHAPE if (!self->resize_in_progress) { self->queue_w = self->width; self->queue_h = self->height; self->resize_in_progress = TRUE; self->queue_reshape = 1; } #else self->relayout = TRUE; #endif puglPostRedisplay(self->view); } /*****************************************************************************/ /* UI scaling */ static void robtk_queue_scale_change (RobWidget *rw, const float ws) { GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); self->queue_widget_scale = ws; queue_draw (rw); } static void set_toplevel_expose_overlay(RobWidget *rw, void (*expose_event)(struct _robwidget*, cairo_t*, cairo_rectangle_t *)){ GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); self->expose_overlay = expose_event; rw->resized = TRUE; //full re-expose queue_draw (rw); } static void robtk_expose_ui_scale (RobWidget* handle, cairo_t* cr, cairo_rectangle_t *ev) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_set_source_rgba (cr, 0, 0, 0, .6); cairo_fill (cr); // ui-scale buttons const int nbtn_col = 4; const int nbtn_row = 2; float bt_w = ev->width / (float)(nbtn_col * 2 + 1); float bt_h = ev->height / (float)(nbtn_row * 2 + 1); static const char scales[8][8] = { "100%", "110%", "115%", "120%", "125%", "150%", "175%", "200%" }; PangoFontDescription *font = pango_font_description_from_string("Sans 24px"); write_text_full (cr, "GUI Scaling", font, floor(ev->width * .5), floor(bt_h * .5), 0, 2, c_wht); pango_font_description_free (font); font = pango_font_description_from_string("Sans 14px"); for (int y = 0; y < nbtn_row; ++y) { for (int x = 0; x < nbtn_col; ++x) { float x0 = floor ((1 + 2 * x) * bt_w); float y0 = floor ((1 + 2 * y) * bt_h); rounded_rectangle (cr, x0, y0, floor (bt_w), floor (bt_h), 8); CairoSetSouerceRGBA(c_wht); cairo_set_line_width(cr, 1.5); cairo_stroke_preserve (cr); cairo_set_source_rgba (cr, .2, .2, .2, 1.0); cairo_fill (cr); int pos = x + y * nbtn_col; write_text_full (cr, scales[pos], font, floor(x0 + bt_w * .5), floor(y0 + bt_h * .5), 0, 2, c_wht); } } pango_font_description_free (font); } static bool robtk_event_ui_scale (RobWidget* rw, RobTkBtnEvent *ev) { const int nbtn_col = 4; const int nbtn_row = 2; float bt_w = rw->area.width / (float)(nbtn_col * 2 + 1); float bt_h = rw->area.height / (float)(nbtn_row * 2 + 1); int xp = floor (ev->x / bt_w); int yp = floor (ev->y / bt_h); if ((xp & 1) == 0 || (yp & 1) == 0) { return FALSE; } const int pos = (xp - 1) / 2 + nbtn_col * (yp - 1) / 2; if (pos < 0 || pos >= nbtn_col * nbtn_row) { // possible rounding-error, bottom right corner return FALSE; } static const float scales[8] = { 1.0, 1.1, 1.15, 1.20, 1.25, 1.50, 1.75, 2.0 }; robtk_queue_scale_change (rw, scales [pos]); return TRUE; } static RobWidget* robtk_tl_mousedown (RobWidget* rw, RobTkBtnEvent *ev) { if (rw->block_events) { if (robtk_event_ui_scale (rw, ev)) { rw->block_events = FALSE; set_toplevel_expose_overlay (rw, NULL); } return NULL; } RobWidget *rv = rcontainer_mousedown (rw, ev); if (rv) return rv; if (ev->button != 3) { return NULL; } RobWidget * c = decend_into_widget_tree(rw, ev->x, ev->y); if (c && c->mousedown) return NULL; rw->block_events = TRUE; set_toplevel_expose_overlay (rw, &robtk_expose_ui_scale); return NULL; } static void robwidget_toplevel_enable_scaling (RobWidget* rw, void (*cb) (RobWidget* w, void* handle), void* handle) { assert (rw->parent == rw); assert (rw->mousedown == &rcontainer_mousedown); robwidget_set_mousedown (rw, robtk_tl_mousedown); GLrobtkLV2UI * const self = (GLrobtkLV2UI*) robwidget_get_toplevel_handle(rw); self->scale_change_cb = cb; self->scale_change_handle = handle; } /*****************************************************************************/ /* helper functions */ static uint64_t microtime(float offset) { struct timespec now; rtk_clock_gettime(&now); now.tv_nsec += 1000000000 * offset; while (now.tv_nsec >= 1000000000) { now.tv_nsec -= 1000000000; now.tv_sec += 1; } return now.tv_sec * 1000 + now.tv_nsec / 1000000; } static void myusleep(uint32_t usec) { #ifdef _WIN32 Sleep(usec / 1000); #else struct timespec slp; slp.tv_sec = usec / 1000000; slp.tv_nsec = (usec % 1000000) * 1000; nanosleep(&slp, NULL); #endif } /*****************************************************************************/ static void reallocate_canvas(GLrobtkLV2UI* self) { #if __BIG_ENDIAN__ float hw_scale = 1.0; #else float hw_scale = puglGetHWSurfaceScale(self->view); #endif #ifdef DEBUG_RESIZE printf("reallocate_canvas to %d x %d\n", self->width, self->height); #endif self->queue_canvas_realloc = false; if (self->cr) { free (self->surf_data); #if __BIG_ENDIAN__ free (self->surf_data_be); #endif cairo_destroy (self->cr); } opengl_reallocate_texture(hw_scale * self->width, hw_scale * self->height, &self->texture_id); if (self->surface) { cairo_surface_destroy (self->surface); self->surface = NULL; } self->cr = opengl_create_cairo_t(hw_scale * self->width, hw_scale * self->height, &self->surface, &self->surf_data); #if __BIG_ENDIAN__ self->surf_data_be = (unsigned char*) calloc (4 * self->width * self->height, sizeof (unsigned char)); #endif /* clear top window */ cairo_save(self->cr); cairo_set_source_rgba (self->cr, .0, .0, .0, 1.0); cairo_set_operator (self->cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle (self->cr, 0, 0, hw_scale * self->width, hw_scale * self->height); cairo_fill (self->cr); cairo_restore(self->cr); } static void onRealReshape(PuglView* view, int width, int height) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); self->resize_in_progress = FALSE; self->resize_toplevel = FALSE; #ifdef DEBUG_RESIZE printf("onRealReshape (%s) %dx%d\n", ROBWIDGET_NAME(self->tl), width, height); #endif switch(plugin_scale_mode(self->ui)) { case LVGL_LAYOUT_TO_FIT: self->xoff = 0; self->yoff = 0; self->xyscale = 1.0; #ifdef DEBUG_RESIZE printf("onRealReshape pre-layout %dx%d -> %dx%d\n", self->width, self->height, width, height); #endif self->width = width; self->height = height; robwidget_layout(self, FALSE, FALSE); self->width = self->tl->area.width; self->height = self->tl->area.height; #ifdef DEBUG_RESIZE printf("onRealReshape post-layout %dx%d\n", self->width, self->height); #endif reallocate_canvas(self); // fall-thru to scale case LVGL_ZOOM_TO_ASPECT: if (self->queue_canvas_realloc) { reallocate_canvas(self); } rtoplevel_cache(self->tl, TRUE); // redraw background if (self->width == width && self->height == height) { self->xoff = 0; self->yoff = 0; self->xyscale = 1.0; glViewport (0, 0, self->width, self->height); } else { reallocate_canvas(self); const float gl_aspect = width / (float) height; const float cl_aspect = self->width / (float) self->height; if (gl_aspect > cl_aspect) { self->xyscale = (float) self->height / (float) height; self->xoff = (width - self->width / self->xyscale)/2; self->yoff = (height - self->height / self->xyscale)/2; } else { self->xoff = 0; self->xyscale = (float) self->width / (float) width; self->xoff = (width - self->width / self->xyscale)/2; self->yoff = (height - self->height / self->xyscale)/2; } glViewport (self->xoff, self->yoff, self->width / self->xyscale, self->height / self->xyscale); } break; case LVGL_CENTER: { self->xyscale = 1.0; self->xoff = (width - self->width)/2; self->yoff = (height - self->height)/2; glViewport (self->xoff, self->yoff, self->width, self->height); } break; case LVGL_TOP_LEFT: { self->xoff = 0; self->yoff = 0; self->xyscale = 1.0; glViewport (0, (height - self->height), self->width, self->height); } break; } glMatrixMode (GL_PROJECTION); glLoadIdentity (); glOrtho (-1.0f, 1.0f, -1.0f, 1.0f, -1.0f, 1.0f); queue_draw_full(self->tl); } /*****************************************************************************/ /* puGL callbacks */ static void onGlInit (PuglView *view) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); #ifdef DEBUG_UI printf("OpenGL version: %s\n", glGetString (GL_VERSION)); printf("OpenGL vendor: %s\n", glGetString (GL_VENDOR)); printf("OpenGL renderer: %s\n", glGetString (GL_RENDERER)); #endif opengl_init(); reallocate_canvas(self); } static void onClose(PuglView* view) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); self->close_ui = TRUE; } // callback from puGL -outsize GLX context(!) when we requested a resize static void onResize(PuglView* view, int *width, int *height, int *set_hints) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); assert(width && height); #ifdef DEBUG_RESIZE printf("onResize( %d x %d -> %d x %d)\n", *width, *height, self->width, self->height); #endif if (*width != self->width || *height != self->height) { self->queue_canvas_realloc = true; } *width = self->width; *height = self->height; if (self->resize_toplevel) { *set_hints = 0; } if (self->extui) { return ; } // all taken care of already if (!self->resize) { return ; } #if (defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE) // schedule resize in parent GUI's thread (idle interface) self->do_the_funky_resize = TRUE; #elif (!defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE) // we can do that here if effectively called by the idle callback of the host self->resize->ui_resize(self->resize->handle, self->width, self->height); #ifdef USE_GTK_RESIZE_HACK printf("GTK window hack: %d %d\n", self->width, self->height); guint w = self->width; guint h = self->height; if (gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))) { gtk_window_resize(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))), w, h); } #endif #else // crash ahead: //self->resize->ui_resize(self->resize->handle, self->width, self->height); #endif } #ifdef TIMED_RESHAPE static void doReshape(GLrobtkLV2UI *self) { self->queue_reshape = 0; onRealReshape(self->view, self->queue_w, self->queue_h); } #endif static void onFileSelected(PuglView* view, const char *filename) { #ifdef RTK_FILE_DIRECT_CALLBACK GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); RTK_FILE_DIRECT_CALLBACK(self->ui, filename); #else // TODO create port event (using urid:map) #endif } static void onFocusChanged(PuglView* view, bool enter) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); if (self->tl->enter_notify && enter) { self->tl->enter_notify (self->tl); } else if (self->tl->leave_notify && !enter) { self->tl->leave_notify (self->tl); } } static void onReshape(PuglView* view, int width, int height) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); if (!self->gl_initialized) { onGlInit(view); self->gl_initialized = true; onRealReshape(view, width, height); return; } #ifdef DEBUG_RESIZE printf("onReshape - %d %d\n", width, height); #endif #ifdef TIMED_RESHAPE if (self->resize_in_progress) { self->queue_reshape = 0; onRealReshape(view, width, height); } else if (!self->queue_reshape) { self->queue_reshape = microtime(.08); } self->queue_w = width; self->queue_h = height; #else onRealReshape(view, width, height); #endif } static void onDisplay(PuglView* view) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); if (!self->gl_initialized) { onGlInit(view); self->gl_initialized = true; onRealReshape(view, self->width, self->height); } #ifdef TIMED_RESHAPE if (self->queue_reshape) { uint64_t now = microtime(0); if (self->queue_reshape < now) { doReshape(self); } } #endif if (self->tl && self->queue_widget_scale != self->tl->widget_scale) { self->tl->widget_scale = self->queue_widget_scale; if (self->scale_change_cb) { self->scale_change_cb (self->tl, self->scale_change_handle); } resize_self (self->tl); robwidget_resize_toplevel (self->tl, self->tl->area.width, self->tl->area.height); } if (self->resize_in_progress) { return; } if (!self->cr) return; // XXX exit failure #ifndef TIMED_RESHAPE if (self->relayout) { self->relayout = FALSE; onRealReshape(view, self->width, self->height); } #endif #ifdef WITH_SIGNATURE if (!self->gpg_verified) { lc_expose(self); } else { cairo_expose(self); } #else cairo_expose(self); #endif cairo_surface_flush(self->surface); #if __BIG_ENDIAN__ int x, y; for (y = 0; y < self->height; ++y) { for (x = 0; x < self->width; ++x) { const int off = 4 * (y * self->width + x); self->surf_data_be[off + 0] = self->surf_data[off + 3]; self->surf_data_be[off + 1] = self->surf_data[off + 2]; self->surf_data_be[off + 2] = self->surf_data[off + 1]; self->surf_data_be[off + 3] = self->surf_data[off + 0]; } } opengl_draw(self->width, self->height, self->surf_data_be, self->texture_id); #else float hw_scale = puglGetHWSurfaceScale(self->view); opengl_draw(hw_scale * self->width, hw_scale * self->height, self->surf_data, self->texture_id); #endif } #define GL_MOUSEBOUNDS \ x = (x - self->xoff) * self->xyscale ; \ y = (y - self->yoff) * self->xyscale ; #define GL_MOUSEEVENT \ RobTkBtnEvent event; \ event.x = x - self->tl->area.x; \ event.y = y - self->tl->area.y; \ event.state = puglGetModifiers(view); \ event.direction = ROBTK_SCROLL_ZERO; \ event.button = -1; static void onMotion(PuglView* view, int x, int y) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); assert(self->tl->mousemove); GL_MOUSEBOUNDS; GL_MOUSEEVENT; //fprintf(stderr, "Motion: %d,%d\n", x, y); if (self->mousefocus && self->mousefocus->mousemove) { offset_traverse_parents(self->mousefocus, &event); self->mousefocus = self->mousefocus->mousemove(self->mousefocus, &event); // proagate ? } else { self->tl->mousemove(self->tl, &event); } #if 1 // do not send enter/leave events when dragging if (self->mousefocus || self->tl->block_events) return; #endif RobWidget *fc = decend_into_widget_tree(self->tl, x, y); if (self->mousehover && fc != self->mousehover && self->mousehover->leave_notify) { self->mousehover->leave_notify(self->mousehover); } if (fc && fc != self->mousehover && fc->enter_notify) { fc->enter_notify(fc); } if (fc && fc->leave_notify) { self->mousehover = fc; } else { self->mousehover = NULL; } } static void onMouse(PuglView* view, int button, bool press, int x, int y) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); #ifdef WITH_SIGNATURE if (!self->gpg_verified) { if (press) { if (self->gpg_shade < .3) self->gpg_shade = .3; else if (self->gpg_shade < .5) self->gpg_shade += .05; puglPostRedisplay(self->view); } else { rtk_open_url (RTK_URI); } return; } #endif //fprintf(stderr, "Mouse %d %s at %d,%d\n", button, press ? "down" : "up", x, y); GL_MOUSEBOUNDS; GL_MOUSEEVENT; event.button = button; if (!press) { if (self->tl->mouseup) { if (self->mousefocus && self->mousefocus->mouseup) { offset_traverse_parents(self->mousefocus, &event); self->mousefocus = self->mousefocus->mouseup(self->mousefocus, &event); } else { self->mousefocus = self->tl->mouseup(self->tl, &event); } } } else { if (x > self->tl->area.x + self->tl->area.width) return; if (y > self->tl->area.y + self->tl->area.height) return; if (x < self->tl->area.x) return; if (y < self->tl->area.y) return; if (self->tl->mousedown) { self->mousefocus = self->tl->mousedown(self->tl, &event); } } } static void onScroll(PuglView* view, int x, int y, float dx, float dy) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)puglGetHandle(view); #ifdef WITH_SIGNATURE if (!self->gpg_verified) return; #endif self->mousefocus = NULL; // CHECK GL_MOUSEBOUNDS; GL_MOUSEEVENT; if (dx < 0) event.direction = ROBTK_SCROLL_LEFT; else if (dx > 0) event.direction = ROBTK_SCROLL_RIGHT; else if (dy < 0) event.direction = ROBTK_SCROLL_DOWN; else if (dy > 0) event.direction = ROBTK_SCROLL_UP; //fprintf(stderr, "Scroll @%d+%d %f %f\n", x, y, dx, dy); if (self->tl->mousescroll) self->tl->mousescroll(self->tl, & event); } /****************************************************************************** * LV2 init/operation */ static int pugl_init(GLrobtkLV2UI* self) { int dflw = self->width; int dflh = self->height; if (self->tl->size_default) { self->tl->size_default(self->tl, &dflw, &dflh); } // Set up GL UI self->view = puglCreate( self->extui ? (PuglNativeWindow) NULL : self->parent, self->extui ? self->extui->plugin_human_id : RTK_URI, self->width, self->height, dflw, dflh, #ifdef LVGL_RESIZEABLE true #else false /* self->extui ? true : false */ #endif , self->ontop , self->transient_id ); if (!self->view) { return -1; } puglSetHandle(self->view, self); puglSetDisplayFunc(self->view, onDisplay); puglSetReshapeFunc(self->view, onReshape); puglSetResizeFunc(self->view, onResize); puglSetFileSelectedFunc(self->view, onFileSelected); if (self->tl->enter_notify || self->tl->leave_notify) { puglSetFocusFunc(self->view, onFocusChanged); } if (self->extui) { puglSetCloseFunc(self->view, onClose); self->ui_closed = self->extui->ui_closed; self->resize = NULL; } if (self->tl->mousemove) { puglSetMotionFunc(self->view, onMotion); } if (self->tl->mousedown || self->tl->mouseup) { puglSetMouseFunc(self->view, onMouse); } if (self->tl->mousescroll) { puglSetScrollFunc(self->view, onScroll); } #ifndef XTERNAL_UI // TODO set minimum size during init in app's thread if (self->resize) { // XXX thread issue self->resize->ui_resize(self->resize->handle, self->width, self->height); } #endif if (self->tl->size_default) { self->tl->size_default(self->tl, &self->width, &self->height); self->resize = NULL; } #ifndef USE_GUI_THREAD ui_enable (self->ui); #endif return 0; } static void pugl_cleanup(GLrobtkLV2UI* self) { #ifndef USE_GUI_THREAD ui_disable (self->ui); #endif glDeleteTextures (1, &self->texture_id); // XXX does his need glxContext ?! free (self->surf_data); #if __BIG_ENDIAN__ free (self->surf_data_be); #endif cairo_destroy (self->cr); puglDestroy(self->view); } ///// /* main-thread to be called at regular intervals */ static int process_gui_events(LV2UI_Handle handle) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle; puglProcessEvents(self->view); if (!self->gl_initialized) { puglPostRedisplay(self->view); } #ifdef TIMED_RESHAPE if (self->queue_reshape > 0) { puglPostRedisplay(self->view); } #endif return 0; } #ifdef USE_GUI_THREAD // we can use idle to call 'ui_resize' in parent's thread context static int idle(LV2UI_Handle handle) { #ifdef HAVE_IDLE_IFACE GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle; if (self->do_the_funky_resize && self->resize) { self->resize->ui_resize(self->resize->handle, self->width, self->height); #ifdef USE_GTK_RESIZE_HACK printf("GTK window hack (idle): %d %d\n", self->width, self->height); guint w = self->width; guint h = self->height; if (gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))) { gtk_window_resize(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(self->resize->handle))), w, h); } #endif self->do_the_funky_resize = FALSE; } #endif return 0; } static void* ui_thread(void* handle) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle; #ifdef THREADSYNC pthread_mutex_lock (&self->msg_thread_lock); #endif #ifdef INIT_PUGL_IN_THREAD if (pugl_init(self)) { self->ui_initialized = -1; } self->ui_initialized = 1; #endif while (!self->exit) { if (self->ui_queue_puglXWindow > 0) { puglShowWindow(self->view); ui_enable(self->ui); self->ui_queue_puglXWindow = 0; } process_gui_events(self); if (self->ui_queue_puglXWindow < 0) { ui_disable(self->ui); puglHideWindow(self->view); self->ui_queue_puglXWindow = 0; } #ifdef THREADSYNC //myusleep(1000000 / 60); // max FPS struct timespec now; rtk_clock_systime(&now); now.tv_nsec += 1000000000 / 25; // min FPS if (now.tv_nsec >= 1000000000) { now.tv_nsec -= 1000000000; now.tv_sec += 1; } assert(now.tv_nsec >= 0 && now.tv_nsec < 1000000000); pthread_cond_timedwait (&self->data_ready, &self->msg_thread_lock, &now); #else myusleep(1000000 / 50); // FPS #endif } #ifdef THREADSYNC pthread_mutex_unlock (&self->msg_thread_lock); #endif #ifdef INIT_PUGL_IN_THREAD pugl_cleanup(self); #endif return NULL; } #endif /****************************************************************************** * LV2 callbacks */ static LV2UI_Handle gl_instantiate(const LV2UI_Descriptor* descriptor, const char* plugin_uri, const char* bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) { #ifdef DEBUG_UI printf("gl_instantiate: %s\n", plugin_uri); #endif GLrobtkLV2UI* self = (GLrobtkLV2UI*)calloc(1, sizeof(GLrobtkLV2UI)); if (!self) { fprintf (stderr, "robtk: out of memory.\n"); return NULL; } self->write = write_function; self->controller = controller; self->view = NULL; self->extui = NULL; self->parent = 0; #ifdef DEFAULT_NOT_ONTOP self->ontop = false; #else self->ontop = true; #endif self->transient_id = 0; self->queue_widget_scale = 1.0; self->queue_canvas_realloc = false; if (getenv("X42_WIDGET_SCALE")) { static const float scales[8] = { 1.0, 1.1, 1.15, 1.20, 1.25, 1.50, 1.75, 2.0 }; float want = atof (getenv("X42_WIDGET_SCALE")); float diff = -1; float best = 1.0; for (int i = 0; i < 8; ++i) { if (diff == -1 || fabsf (want - scales[i]) < diff) { diff = fabsf (want - scales[i]); best = scales[i]; } } self->queue_widget_scale = best; } #if (defined USE_GUI_THREAD && defined THREADSYNC) pthread_mutex_init(&self->msg_thread_lock, NULL); pthread_cond_init(&self->data_ready, NULL); #endif const LV2_Options_Option* options = NULL; LV2_URID_Map* map = NULL; for (int i = 0; features && features[i]; ++i) { if (!strcmp(features[i]->URI, LV2_UI__parent)) { self->parent = (PuglNativeWindow)features[i]->data; } else if (!strcmp(features[i]->URI, LV2_UI__resize)) { self->resize = (LV2UI_Resize*)features[i]->data; } else if (!strcmp(features[i]->URI, LV2_URID__map)) { map = (LV2_URID_Map*)features[i]->data; } else if (!strcmp(features[i]->URI, LV2_OPTIONS__options)) { options = (LV2_Options_Option*)features[i]->data; } #ifdef XTERNAL_UI else if (!strcmp(features[i]->URI, LV2_EXTERNAL_UI_URI) && !self->extui) { self->extui = (struct lv2_external_ui_host*) features[i]->data; } else if (!strcmp(features[i]->URI, LV2_EXTERNAL_UI_URI__KX__Host)) { self->extui = (struct lv2_external_ui_host*) features[i]->data; #ifdef DEBUG_UI } else { printf("Feature: '%s'\n", features[i]->URI); #endif } #endif } if (options && map) { LV2_URID atom_Long = map->map(map->handle, LV2_ATOM__Long); LV2_URID transient_for = map->map (map->handle, "http://kxstudio.sf.net/ns/lv2ext/props#TransientWindowId"); #ifdef RTK_USE_HOST_COLORS LV2_URID atom_Int = map->map(map->handle, LV2_ATOM__Int); LV2_URID color_fg = map->map (map->handle, "http://lv2plug.in/ns/extensions/ui#foregroundColor"); LV2_URID color_bg = map->map (map->handle, "http://lv2plug.in/ns/extensions/ui#backgroundColor"); #endif for (const LV2_Options_Option* o = options; o->key; ++o) { if (o->context == LV2_OPTIONS_INSTANCE && o->key == transient_for && o->type == atom_Long) { self->transient_id = *(const unsigned long*)o->value; } #ifdef RTK_USE_HOST_COLORS // TODO: consider adding a host bg colored border when the plugin does not use RTK_USE_HOST_COLORS else if (o->context == LV2_OPTIONS_INSTANCE && o->key == color_fg && o->type == atom_Int) { set_host_color (0, *(const uint32_t*)o->value); } else if (o->context == LV2_OPTIONS_INSTANCE && o->key == color_bg && o->type == atom_Int) { set_host_color (1, *(const uint32_t*)o->value); } #endif } } if (self->transient_id != 0) { self->ontop = false; } if (getenv("X42_ON_TOP")) { self->ontop = 0 != atoi (getenv("X42_ON_TOP")); } if (!self->parent && !self->extui) { fprintf(stderr, "error: No parent window provided.\n"); free(self); return NULL; } self->ui_closed = NULL; self->close_ui = FALSE; self->rb = posrb_alloc(sizeof(RWArea) * 48); // depends on plugin and threading stategy #ifdef WITH_SIGNATURE self->gpg_shade = 0; # include "gpg_check.c" #endif self->tl = NULL; self->ui = instantiate(self, descriptor, plugin_uri, bundle_path, write_function, controller, &self->tl, features); if (!self->ui) { posrb_free(self->rb); free(self); return NULL; } if (!self->tl || !self->tl->expose_event || !self->tl->size_request) { posrb_free(self->rb); free(self); return NULL; } robwidget_layout(self, TRUE, TRUE); assert(self->width > 0 && self->height > 0); self->cr = NULL; self->surface= NULL; self->surf_data = NULL; // ditto #if __BIG_ENDIAN__ self->surf_data_be = NULL; #endif self->texture_id = 0; // already too much of this to keep valgrind happy self->xoff = self->yoff = 0; self->xyscale = 1.0; self->gl_initialized = 0; self->expose_area.x = 0; self->expose_area.y = 0; self->expose_area.width = self->width; self->expose_area.height = self->height; self->mousefocus = NULL; self->mousehover = NULL; self->resize_in_progress = FALSE; self->resize_toplevel = FALSE; #ifdef INIT_PUGL_IN_THREAD self->ui_initialized = 0; #endif #ifdef TIMED_RESHAPE self->queue_reshape = 0; self->queue_w = 0; self->queue_h = 0; #else self->relayout = FALSE; #endif #if (defined USE_GUI_THREAD && defined HAVE_IDLE_IFACE) self->do_the_funky_resize = FALSE; #endif #if (!defined USE_GUI_THREAD) || (!defined INIT_PUGL_IN_THREAD) if (pugl_init(self)) { return NULL; } #endif #ifdef USE_GUI_THREAD self->ui_queue_puglXWindow = 0; self->exit = false; pthread_create(&self->thread, NULL, ui_thread, self); #ifdef INIT_PUGL_IN_THREAD while (!self->ui_initialized) { myusleep(1000); sched_yield(); } if (self->ui_initialized < 0) { self->exit = true; pthread_join(self->thread, NULL); return NULL; } #endif #endif #ifdef XTERNAL_UI if (self->extui) { #ifdef DEBUG_UI printf("robtk: Xternal UI\n"); #endif self->xternal_ui.run = &x_run; self->xternal_ui.show = &x_show; self->xternal_ui.hide = &x_hide; self->xternal_ui.self = (void*) self; *widget = (void*) &self->xternal_ui; } else #endif { #ifdef DEBUG_UI printf("robtk: Interal UI\n"); #endif *widget = (void*)puglGetNativeWindow(self->view); } return self; } static void gl_cleanup(LV2UI_Handle handle) { GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle; #ifdef USE_GUI_THREAD self->exit = true; pthread_join(self->thread, NULL); #endif #if (!defined USE_GUI_THREAD) || (!defined INIT_PUGL_IN_THREAD) pugl_cleanup(self); #endif #if (defined USE_GUI_THREAD && defined THREADSYNC) pthread_mutex_destroy(&self->msg_thread_lock); pthread_cond_destroy(&self->data_ready); #endif if (self->surface) { cairo_surface_destroy (self->surface); self->surface = NULL; } cleanup(self->ui); posrb_free(self->rb); free(self); } static void gl_port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void* buffer) { /* these arrive in GUI context of the parent thread * -- could do some trickery here, too */ GLrobtkLV2UI* self = (GLrobtkLV2UI*)handle; port_event(self->ui, port_index, buffer_size, format, buffer); #if (defined USE_GUI_THREAD && defined THREADSYNC) if (pthread_mutex_trylock (&self->msg_thread_lock) == 0) { pthread_cond_signal (&self->data_ready); pthread_mutex_unlock (&self->msg_thread_lock); } #endif } /****************************************************************************** * LV2 setup */ #ifdef HAVE_IDLE_IFACE #ifdef USE_GUI_THREAD static const LV2UI_Idle_Interface idle_iface = { idle }; #else static const LV2UI_Idle_Interface idle_iface = { process_gui_events }; #endif #endif static const void* gl_extension_data(const char* uri) { #ifdef HAVE_IDLE_IFACE if (!strcmp(uri, LV2_UI__idleInterface)) { return &idle_iface; } else #endif return extension_data(uri); } static const LV2UI_Descriptor gl_descriptor = { RTK_URI RTK_GUI "_gl", gl_instantiate, gl_cleanup, gl_port_event, gl_extension_data }; #ifdef RTK_DESCRIPTOR // standalone lv2 const LV2UI_Descriptor* RTK_DESCRIPTOR(uint32_t index) { return &gl_descriptor; } #else #undef LV2_SYMBOL_EXPORT #ifdef _WIN32 # define LV2_SYMBOL_EXPORT __declspec(dllexport) #else # define LV2_SYMBOL_EXPORT __attribute__ ((visibility ("default"))) #endif LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { #if (defined _WIN32 && defined RTK_STATIC_INIT) static int once = 0; if (!once) {once = 1; gobject_init_ctor();} #endif switch (index) { case 0: return &gl_descriptor; default: return NULL; } } #if (defined _WIN32 && defined RTK_STATIC_INIT) static void __attribute__((constructor)) x42_init() { pthread_win32_process_attach_np(); } static void __attribute__((destructor)) x42_fini() { pthread_win32_process_detach_np(); } #endif #endif robtk-0.8.5/ui_gtk.c000066400000000000000000000105341463170413500143020ustar00rootroot00000000000000/* robTK * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA. */ #define USE_GTK_RESIZE_HACK #define _XOPEN_SOURCE 600 #include #include #include #include #include #warning **************************************************** #warning *** GTK UI IS DEPRECATED DO NOT USE THIS ANYMORE *** #warning **************************************************** /*****************************************************************************/ #define ROBTK_MOD_SHIFT GDK_SHIFT_MASK #define ROBTK_MOD_CTRL GDK_CONTROL_MASK #define GTK_BACKEND #include "robtk.h" static void robtk_close_self(void *h) { // TODO } static int robtk_open_file_dialog(void *h, const char *title) { return -1; // TODO } static void robwidget_toplevel_enable_scaling (RobWidget* rw, void (*cb) (RobWidget* w, void* h), void* handle) { ; } static void robtk_queue_scale_change (RobWidget *rw, const float ws) { ; } /*****************************************************************************/ #include PLUGIN_SOURCE /*****************************************************************************/ typedef struct { RobWidget* tl; LV2UI_Handle ui; } GtkMetersLV2UI; static const char * robtk_info(void *h) { return "v" VERSION; } /****************************************************************************** * LV2 callbacks */ static LV2UI_Handle gtk_instantiate(const LV2UI_Descriptor* descriptor, const char* plugin_uri, const char* bundle_path, LV2UI_Write_Function write_function, LV2UI_Controller controller, LV2UI_Widget* widget, const LV2_Feature* const* features) { GtkMetersLV2UI* self = (GtkMetersLV2UI*)calloc(1, sizeof(GtkMetersLV2UI)); GtkWidget *parent = NULL; *widget = NULL; for (int i = 0; features && features[i]; ++i) { if (!strcmp(features[i]->URI, LV2_UI__parent)) { parent = (GtkWidget*)features[i]->data; } } self->ui = instantiate(self, descriptor, plugin_uri, bundle_path, write_function, controller, &self->tl, features); if (!self->ui) { free(self); return NULL; } *widget = self->tl->c; gtk_widget_show(self->tl->c); if (self->tl->size_default && parent) { int w, h; self->tl->size_default(self->tl, &w, &h); // TODO add existing top-level window size // somehow set default size of *widget only if (gtk_widget_get_toplevel(GTK_WIDGET(parent))) { gtk_window_resize(GTK_WINDOW(gtk_widget_get_toplevel(GTK_WIDGET(parent))), w, h); } } return self; } static void gtk_cleanup(LV2UI_Handle handle) { GtkMetersLV2UI* self = (GtkMetersLV2UI*)handle; cleanup(self->ui); free(self); } static void gtk_port_event(LV2UI_Handle handle, uint32_t port_index, uint32_t buffer_size, uint32_t format, const void* buffer) { GtkMetersLV2UI* self = (GtkMetersLV2UI*)handle; port_event(self->ui, port_index, buffer_size, format, buffer); } /****************************************************************************** * LV2 setup */ static const void* gtk_extension_data(const char* uri) { return extension_data(uri); } static const LV2UI_Descriptor gtk_descriptor = { RTK_URI RTK_GUI "_gtk", gtk_instantiate, gtk_cleanup, gtk_port_event, gtk_extension_data }; #undef LV2_SYMBOL_EXPORT #ifdef _WIN32 # define LV2_SYMBOL_EXPORT __declspec(dllexport) #else # define LV2_SYMBOL_EXPORT __attribute__ ((visibility ("default"))) #endif LV2_SYMBOL_EXPORT const LV2UI_Descriptor* lv2ui_descriptor(uint32_t index) { switch (index) { case 0: return >k_descriptor; default: return NULL; } } robtk-0.8.5/weakjack/000077500000000000000000000000001463170413500144315ustar00rootroot00000000000000robtk-0.8.5/weakjack/weak_libjack.c000066400000000000000000000155271463170413500172150ustar00rootroot00000000000000/* runtime/weak dynamic JACK linking * * (C) 2014 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #include "weak_libjack.h" #ifndef USE_WEAK_JACK int have_libjack (void) { return 0; } #else #include #include #include #ifdef _WIN32 #include #else #include #endif static void* lib_open(const char* const so) { #ifdef _WIN32 return (void*) LoadLibraryA(so); #else return dlopen(so, RTLD_NOW|RTLD_LOCAL); #endif } static void* lib_symbol(void* const lib, const char* const sym) { #ifdef _WIN32 return (void*) GetProcAddress((HMODULE)lib, sym); #else return dlsym(lib, sym); #endif } #if _MSC_VER && !__INTEL_COMPILER typedef void * pvoid_t; #define MAPSYM(SYM, FAIL) _j._ ## SYM = (func_t)lib_symbol(lib, "jack_" # SYM); \ if (!_j._ ## SYM) err |= FAIL; #elif defined NDEBUG typedef void * __attribute__ ((__may_alias__)) pvoid_t; #define MAPSYM(SYM, FAIL) *(pvoid_t *)(&_j._ ## SYM) = lib_symbol(lib, "jack_" # SYM); \ if (!_j._ ## SYM) err |= FAIL; #else typedef void * __attribute__ ((__may_alias__)) pvoid_t; #define MAPSYM(SYM, FAIL) *(pvoid_t *)(&_j._ ## SYM) = lib_symbol(lib, "jack_" # SYM); \ if (!_j._ ## SYM) { \ if (FAIL) { \ fprintf(stderr, "*** WEAK-JACK: required symbol 'jack_%s' was not found\n", "" # SYM); \ } \ err |= FAIL; \ } #endif typedef void (* func_t) (void); /* function pointers to the real jack API */ static struct WeakJack { func_t _client_open; // special case due to varargs #define JCFUN(ERR, RTYPE, NAME, RVAL) func_t _ ## NAME ; #define JPFUN(ERR, RTYPE, NAME, DEF, ARGS, RVAL) func_t _ ## NAME ; #define JXFUN(ERR, RTYPE, NAME, DEF, ARGS, CODE) func_t _ ## NAME ; #define JVFUN(ERR, NAME, DEF, ARGS, CODE) func_t _ ## NAME ; #include "weak_libjack.def" #undef JCFUN #undef JPFUN #undef JXFUN #undef JVFUN } _j; static int _status = -1; __attribute__((constructor)) static void init_weak_jack(void) { void* lib; int err = 0; #ifndef NDEBUG fprintf(stderr, "*** WEAK-JACK: initializing\n"); #endif memset(&_j, 0, sizeof(_j)); #ifdef __APPLE__ lib = lib_open("libjack.dylib"); if (!lib) { lib = lib_open("/usr/local/lib/libjack.dylib"); } #elif (defined _WIN32) # ifdef __x86_64__ lib = lib_open("libjack64.dll"); # else lib = lib_open("libjack.dll"); # endif #else lib = lib_open("libjack.so.0"); #endif if (!lib) { #ifndef NDEBUG fprintf(stderr, "*** WEAK-JACK: libjack was not found\n"); #endif _status = -2; return; } /* found library, now lookup functions */ MAPSYM(client_open, 2) #define JCFUN(ERR, RTYPE, NAME, RVAL) MAPSYM(NAME, ERR) #define JPFUN(ERR, RTYPE, NAME, DEF, ARGS, RVAL) MAPSYM(NAME, ERR) #define JXFUN(ERR, RTYPE, NAME, DEF, ARGS, CODE) MAPSYM(NAME, ERR) #define JVFUN(ERR, NAME, DEF, ARGS, CODE) MAPSYM(NAME, ERR) #include "weak_libjack.def" #undef JCFUN #undef JPFUN #undef JXFUN #undef JVFUN /* if a required symbol is not found, disable JACK completly */ if (err) { _j._client_open = NULL; } _status = err; #ifndef NDEBUG fprintf(stderr, "*** WEAK-JACK: %s. (%d)\n", err ? "jack is not available" : "OK", _status); #endif } int have_libjack (void) { if (_status == -1) { init_weak_jack(); } return _status; } /******************************************************************************* * helper macros */ #if defined(__GNUC__) && (__GNUC__ > 2) && !defined(NDEBUG) #define likely(expr) (__builtin_expect (!!(expr), 1)) #else #define likely(expr) (expr) #endif #ifndef NDEBUG # define WJACK_WARNING(NAME) \ fprintf(stderr, "*** WEAK-JACK: function 'jack_%s' ignored\n", "" # NAME); #else # define WJACK_WARNING(NAME) ; #endif /****************************************************************************** * JACK API wrapper functions. * * if a function pointer is set in the static struct WeakJack _j, * the function is called directly. * Otherwise a dummy NOOP implementation is provided. * The latter is mainly for compile-time warnings. * * If libjack is not found, jack_client_open() will fail. * In that case the application should not call any other libjack * functions. Hence a real implementation is not needed. * (jack ringbuffer may be an exception for some apps) */ /* dedicated support for jack_client_open(,..) variable arg function macro */ func_t WJACK_get_client_open(void) { if (_status == -1) { init_weak_jack(); } return _j._client_open; } /* callback to set status */ jack_client_t * WJACK_no_client_open (const char *client_name, jack_options_t options, jack_status_t *status, ...) { WJACK_WARNING(client_open); if (status) { *status = JackFailure; } return NULL; } /******************************************************************************* * Macros to wrap jack API */ /* abstraction for jack_client functions * rtype jack_function_name (jack_client_t *client) { return rval; } */ #define JCFUN(ERR, RTYPE, NAME, RVAL) \ RTYPE WJACK_ ## NAME (jack_client_t *client) { \ if likely(_j._ ## NAME) { \ return ((RTYPE (*)(jack_client_t *client)) _j._ ## NAME)(client); \ } else { \ WJACK_WARNING(NAME) \ return RVAL; \ } \ } /* abstraction for NOOP functions with return value * rtype jack_function_name (ARGS) { return rval; } */ #define JPFUN(ERR, RTYPE, NAME, DEF, ARGS, RVAL) \ RTYPE WJACK_ ## NAME DEF { \ if likely(_j._ ## NAME) { \ return ((RTYPE (*)DEF) _j._ ## NAME) ARGS; \ } else { \ WJACK_WARNING(NAME) \ return RVAL; \ } \ } /* abstraction for functions that need custom code. * e.g. functions with return-value-pointer args, * use CODE to initialize value * * rtype jack_function_name (ARGS) { CODE } */ #define JXFUN(ERR, RTYPE, NAME, DEF, ARGS, CODE) \ RTYPE WJACK_ ## NAME DEF { \ if likely(_j._ ## NAME) { \ return ((RTYPE (*)DEF) _j._ ## NAME) ARGS; \ } else { \ WJACK_WARNING(NAME) \ CODE \ } \ } /* abstraction for void functions with return-value-pointer args * void jack_function_name (ARGS) { CODE } */ #define JVFUN(ERR, NAME, DEF, ARGS, CODE) \ void WJACK_ ## NAME DEF { \ if likely(_j._ ## NAME) { \ ((void (*)DEF) _j._ ## NAME) ARGS; \ } else { \ WJACK_WARNING(NAME) \ CODE \ } \ } #include "weak_libjack.def" #undef JCFUN #undef JPFUN #undef JXFUN #undef JVFUN #endif // end USE_WEAK_JACK robtk-0.8.5/weakjack/weak_libjack.def000066400000000000000000000251161463170413500175240ustar00rootroot00000000000000/* macro-absraction of the JACK API * * see weak_libjack.c for details, in general arguments are: * * [required], [return type], [name], [arguments], [code or return value] * * This file is included multiple times with different macro definitions * do not add header guards. * see https://en.wikibooks.org/wiki/C_Programming/Preprocessor#X-Macros */ #ifdef USE_WEAK_JACK /* */ JCFUN(1, int, client_close, 0) JCFUN(1, char*, get_client_name, NULL) JVFUN(0, on_shutdown, (jack_client_t *c, JackShutdownCallback s, void *a), (c,s,a),) JVFUN(0, on_info_shutdown, (jack_client_t *c, JackInfoShutdownCallback s, void *a), (c,s,a),) JPFUN(1, int, set_process_callback, (jack_client_t *c, JackProcessCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_freewheel_callback, (jack_client_t *c, JackFreewheelCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_buffer_size_callback, (jack_client_t *c, JackBufferSizeCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_sample_rate_callback, (jack_client_t *c, JackSampleRateCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_port_registration_callback, (jack_client_t *c, JackPortRegistrationCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_port_connect_callback, (jack_client_t *c, JackPortConnectCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_graph_order_callback, (jack_client_t *c, JackGraphOrderCallback g, void *a), (c,g,a), -1) JPFUN(1, int, set_xrun_callback, (jack_client_t *c, JackXRunCallback g, void *a), (c,g,a), -1) JPFUN(1, int, set_latency_callback, (jack_client_t *c, JackLatencyCallback g, void *a), (c,g,a), -1) JVFUN(1, set_error_function, (void (*f)(const char *)), (f),) JVFUN(1, set_info_function, (void (*f)(const char *)), (f),) JCFUN(1, int, activate, -1) JCFUN(1, int, deactivate, -1) JPFUN(1, int, client_name_size, (), (), 32) JCFUN(1, jack_nframes_t, get_sample_rate, 0) JCFUN(1, jack_nframes_t, get_buffer_size, 0) JPFUN(1, jack_nframes_t, frames_since_cycle_start, (const jack_client_t *c), (c), 0) JPFUN(1, jack_nframes_t, frame_time, (const jack_client_t *c), (c), 0) JPFUN(1, jack_nframes_t, last_frame_time, (const jack_client_t *c), (c), 0) JPFUN(1, jack_time_t, get_time, (void), (), 0) JCFUN(1, float, cpu_load, 0) JCFUN(1, int, is_realtime, 0) JPFUN(1, int, set_freewheel, (jack_client_t *c, int o), (c,o), 0) JPFUN(1, int, set_buffer_size, (jack_client_t *c, jack_nframes_t b), (c,b), 0) JCFUN(0, int, recompute_total_latencies, 0) JPFUN(0, jack_nframes_t, port_get_total_latency, (jack_client_t *c, jack_port_t *p), (c,p), 0) JVFUN(0, port_get_latency_range, (jack_port_t *p, jack_latency_callback_mode_t m, jack_latency_range_t *r), (p,m,r), if (r) {r->min = r->max = 0;}) JVFUN(0, port_set_latency_range, (jack_port_t *p, jack_latency_callback_mode_t m, jack_latency_range_t *r), (p,m,r),) JPFUN(1, void*, port_get_buffer, (jack_port_t *p, jack_nframes_t n), (p,n), NULL) JPFUN(1, int, port_request_monitor, (jack_port_t *p, int o), (p,o), 0) JPFUN(1, int, port_ensure_monitor, (jack_port_t *p, int o), (p,o), 0) JPFUN(1, int, port_monitoring_input, (jack_port_t *p), (p), 0) JPFUN(1, const char*, port_name, (const jack_port_t *p), (p), NULL) JPFUN(1, const char*, port_short_name, (const jack_port_t *p), (p), NULL) JPFUN(1, int, port_flags, (const jack_port_t *p), (p), 0) JPFUN(1, const char**, get_ports,(jack_client_t *c, const char *p, const char *t, unsigned long f), (c,p,t,f), NULL) JPFUN(1, int, port_name_size, (void), (), 0) JPFUN(1, int, port_type_size, (void), (), 0) JPFUN(1, size_t, port_type_get_buffer_size, (jack_client_t *c, const char *t), (c,t), 0) JPFUN(1, jack_port_t*, port_by_name, (jack_client_t *c, const char *n), (c,n), NULL) JPFUN(1, jack_port_t*, port_by_id, (jack_client_t *c, jack_port_id_t i), (c,i), NULL) JPFUN(1, jack_port_t*, port_register, (jack_client_t *c, const char *n, const char *t, unsigned long f, unsigned long b), (c,n,t,f,b), NULL) JPFUN(1, int, port_unregister, (jack_client_t *c, jack_port_t *p), (c,p), 0) JPFUN(1, const char *, port_type, (const jack_port_t *p), (p), 0) JPFUN(1, const char **, port_get_connections, (const jack_port_t *p), (p), 0) JPFUN(1, const char **, port_get_all_connections, (const jack_client_t *c, const jack_port_t *p), (c,p), 0) JPFUN(1, int, port_set_name, (jack_port_t *p, const char *n), (p,n), -1) JXFUN(0, int, port_rename, (jack_client_t *c, jack_port_t *p, const char *n), (c,p,n), return jack_port_set_name (p,n);) JPFUN(1, int, port_get_aliases, (const jack_port_t *port, char* const aliases[2]), (port,aliases), 0) JPFUN(1, int, port_disconnect, (jack_client_t *c, jack_port_t *p), (c,p), 0) JPFUN(1, int, connect, (jack_client_t *c, const char *s, const char *d), (c,s,d), -1) JPFUN(1, int, disconnect, (jack_client_t *c, const char *s, const char *d), (c,s,d), -1) JVFUN(0, free, (void *p), (p), free(p);) JCFUN(1, jack_nframes_t, cycle_wait, 0) JVFUN(1, cycle_signal, (jack_client_t *c, int s), (c,s),) JPFUN(1, int, set_process_thread, (jack_client_t *c, JackThreadCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_thread_init_callback, (jack_client_t *c, JackThreadInitCallback p, void *a), (c,p,a), -1) JPFUN(1, int, transport_locate, (jack_client_t *c, jack_nframes_t f), (c,f), 0) JVFUN(1, transport_start, (jack_client_t *c), (c),) JVFUN(1, transport_stop, (jack_client_t *c), (c),) JPFUN(1, jack_nframes_t, get_current_transport_frame, (const jack_client_t *c), (c), 0) JXFUN(1, jack_transport_state_t, transport_query, (const jack_client_t *c, jack_position_t *p), (c,p), memset(p, 0, sizeof(jack_position_t)); return JackTransportStopped;) JPFUN(1, int, set_sync_callback, (jack_client_t *c, JackSyncCallback p, void *a), (c,p,a), -1) JPFUN(1, int, set_timebase_callback, (jack_client_t *c, int l, JackTimebaseCallback p, void *a), (c,l,p,a), -1) JCFUN(1, int, release_timebase, 0) /* */ JPFUN(1, uint32_t, midi_get_event_count, (void* p), (p), 0) JPFUN(1, int, midi_event_get, (jack_midi_event_t *e, void *p, uint32_t i), (e,p,i), -1) JPFUN(1, int, midi_event_write, (void *b, jack_nframes_t t, const jack_midi_data_t *d, size_t s), (b,t,d,s), -1) JVFUN(1, midi_clear_buffer, (void *b), (b),) /* */ JPFUN(0, int, set_session_callback, (jack_client_t *c, JackSessionCallback s, void *a), (c,s,a), -1) JPFUN(0, int, session_reply, (jack_client_t *c, jack_session_event_t *e), (c,e), -1) JVFUN(0, session_event_free, (jack_session_event_t *e), (e), ) /* */ JPFUN(1, jack_ringbuffer_t *, ringbuffer_create, (size_t s), (s), NULL) JVFUN(1, ringbuffer_free, (jack_ringbuffer_t *rb), (rb), ) JVFUN(1, ringbuffer_reset, (jack_ringbuffer_t *rb), (rb), ) JVFUN(1, ringbuffer_read_advance, (jack_ringbuffer_t *rb, size_t c), (rb,c), ) JVFUN(1, ringbuffer_write_advance, (jack_ringbuffer_t *rb, size_t c), (rb,c), ) JPFUN(1, size_t, ringbuffer_read_space, (const jack_ringbuffer_t *rb), (rb), 0) JPFUN(1, size_t, ringbuffer_write_space, (const jack_ringbuffer_t *rb), (rb), 0) JPFUN(1, size_t, ringbuffer_read, (jack_ringbuffer_t *rb, char *d, size_t c), (rb,d,c), 0) JPFUN(1, size_t, ringbuffer_write, (jack_ringbuffer_t *rb, const char *s, size_t c), (rb,s,c), 0) JPFUN(0, int, ringbuffer_mlock, (jack_ringbuffer_t *rb), (rb), 0) JVFUN(0, ringbuffer_get_read_vector, (const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *v), (rb,v), if (v) {v->buf=NULL; v->len=0;} ) JVFUN(0, ringbuffer_get_write_vector, (const jack_ringbuffer_t *rb, jack_ringbuffer_data_t *v), (rb,v), if (v) {v->buf=NULL; v->len=0;} ) JPFUN(0, size_t, ringbuffer_peek, (jack_ringbuffer_t *rb, char *d, size_t c), (rb,d,c), 0) /* */ JCFUN(0, int, client_real_time_priority, 0) JCFUN(0, int, client_max_real_time_priority, 0) JPFUN(0, int, acquire_real_time_scheduling, (jack_native_thread_t t, int p), (t,p), 0) JPFUN(0, int, drop_real_time_scheduling, (jack_native_thread_t t), (t), 0) JPFUN(0, int, client_stop_thread, (jack_client_t* c, jack_native_thread_t t), (c,t), 0) JPFUN(0, int, client_kill_thread, (jack_client_t* c, jack_native_thread_t t), (c,t), 0) #ifndef _WIN32 JVFUN(0, set_thread_creator, (jack_thread_creator_t c), (c),) #endif JPFUN(1, int, client_create_thread, \ (jack_client_t* c, jack_native_thread_t *t, int p, int r, void *(*f)(void*), void *a), (c,t,p,r,f,a), 0) #ifndef NO_JACK_METADATA /* - TODO*/ /* */ JPFUN(0, char *, get_uuid_for_client_name, (jack_client_t* c, const char* n), (c,n), NULL) JPFUN(0, char *, get_client_name_by_uuid, (jack_client_t* c, const char* u), (c,u), NULL) JPFUN(0, jack_uuid_t, port_uuid, (const jack_port_t *p), (p), 0) /* */ JPFUN(0, int, set_property, (jack_client_t* c, jack_uuid_t s, const char* k, const char* v, const char* t), (c,s,k,v,t), -1) JXFUN(0, int, get_property, (jack_uuid_t s, const char* k, char** v, char** t), (s,k,v,t), if (v) *v=NULL; if (t) *t=NULL; return -1;) JVFUN(0, free_description, (jack_description_t* d, int f), (d,f),) JXFUN(0, int, get_properties, (jack_uuid_t s, jack_description_t* d), (s,d), if (d) {d->properties = NULL; d->property_cnt = 0;} return -1;) JXFUN(0, int, get_all_properties, (jack_description_t** d), (d), if (d) *d=NULL; return -1;) JPFUN(0, int, remove_property, (jack_client_t* c, jack_uuid_t s, const char* k), (c,s,k), -1) JPFUN(0, int, remove_properties, (jack_client_t* c, jack_uuid_t s), (c,s), -1) JPFUN(0, int, remove_all_properties, (jack_client_t* c), (c), -1) JPFUN(0, int, set_property_change_callback, (jack_client_t *c, JackPropertyChangeCallback s, void *a), (c,s,a), -1) #endif /* */ JCFUN(1, float, get_max_delayed_usecs, 0.0) JCFUN(1, float, get_xrun_delayed_usecs, 0.0) JVFUN(0, reset_max_delayed_usecs, (jack_client_t *c), (c),) #endif // end USE_WEAK_JACK robtk-0.8.5/weakjack/weak_libjack.h000066400000000000000000000236251463170413500172200ustar00rootroot00000000000000/* runtime/weak dynamic JACK linking * * (C) 2014 Robin Gareus * * The wrapped jack API itself is * (C) 2001 Paul Davis * (C) 2004 Jack O'Quin * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _WEAK_JACK_H #define _WEAK_JACK_H #ifdef __cplusplus extern "C" { #endif /** check if libjack is available * * return 0 if libjack is dynamically linked of was * successfully dl-opened. Otherwise: * * -1: library was not initialized * -2: libjack was not found * > 0 bitwise flags: * 1: a required function was not found in libjack * 2: jack_client_open was not found in libjack */ int have_libjack(void); #ifdef __cplusplus } #endif #ifdef USE_WEAK_JACK /* */ #define jack_client_close WJACK_client_close #define jack_get_client_name WJACK_get_client_name #define jack_get_sample_rate WJACK_get_sample_rate #define jack_get_buffer_size WJACK_get_buffer_size #define jack_frames_since_cycle_start WJACK_frames_since_cycle_start #define jack_frame_time WJACK_frame_time #define jack_last_frame_time WJACK_last_frame_time #define jack_get_time WJACK_get_time #define jack_cpu_load WJACK_cpu_load #define jack_is_realtime WJACK_is_realtime #define jack_client_name_size WJACK_client_name_size #define jack_set_freewheel WJACK_set_freewheel #define jack_set_buffer_size WJACK_set_buffer_size #define jack_on_shutdown WJACK_on_shutdown #define jack_on_info_shutdown WJACK_on_info_shutdown #define jack_set_process_callback WJACK_set_process_callback #define jack_set_freewheel_callback WJACK_set_freewheel_callback #define jack_set_buffer_size_callback WJACK_set_buffer_size_callback #define jack_set_sample_rate_callback WJACK_set_sample_rate_callback #define jack_set_port_registration_callback WJACK_set_port_registration_callback #define jack_set_port_connect_callback WJACK_set_port_connect_callback #define jack_set_graph_order_callback WJACK_set_graph_order_callback #define jack_set_xrun_callback WJACK_set_xrun_callback #define jack_set_latency_callback WJACK_set_latency_callback #define jack_set_error_function WJACK_set_error_function #define jack_set_info_function WJACK_set_info_function #define jack_activate WJACK_activate #define jack_deactivate WJACK_deactivate #define jack_recompute_total_latencies WJACK_recompute_total_latencies #define jack_port_get_total_latency WJACK_port_get_total_latency #define jack_port_get_latency_range WJACK_port_get_latency_range #define jack_port_set_latency_range WJACK_port_set_latency_range #define jack_port_get_buffer WJACK_port_get_buffer #define jack_port_request_monitor WJACK_port_request_monitor #define jack_port_ensure_monitor WJACK_port_ensure_monitor #define jack_port_monitoring_input WJACK_port_monitoring_input #define jack_port_name WJACK_port_name #define jack_port_short_name WJACK_port_short_name #define jack_port_flags WJACK_port_flags #define jack_get_ports WJACK_get_ports #define jack_port_name_size WJACK_port_name_size #define jack_port_type_size WJACK_port_type_size #define jack_port_type_get_buffer_size WJACK_port_type_get_buffer_size #define jack_port_by_name WJACK_port_by_name #define jack_port_by_id WJACK_port_by_id #define jack_port_set_name WJACK_port_set_name #define jack_port_get_aliases WJACK_port_get_aliases #define jack_port_rename WJACK_port_rename #define jack_port_disconnect WJACK_port_disconnect #define jack_port_register WJACK_port_register #define jack_port_unregister WJACK_port_unregister #define jack_port_type WJACK_port_type #define jack_port_get_connections WJACK_port_get_connections #define jack_port_get_all_connections WJACK_port_get_all_connections #define jack_connect WJACK_connect #define jack_disconnect WJACK_disconnect #define jack_free WJACK_free #define jack_cycle_wait WJACK_cycle_wait #define jack_cycle_signal WJACK_cycle_signal #define jack_set_process_thread WJACK_set_process_thread #define jack_set_thread_init_callback WJACK_set_thread_init_callback /* */ #define jack_get_current_transport_frame WJACK_get_current_transport_frame #define jack_transport_locate WJACK_transport_locate #define jack_transport_start WJACK_transport_start #define jack_transport_stop WJACK_transport_stop #define jack_transport_query WJACK_transport_query #define jack_set_sync_callback WJACK_set_sync_callback #define jack_set_timebase_callback WJACK_set_timebase_callback #define jack_release_timebase WJACK_release_timebase /* */ #define jack_midi_get_event_count WJACK_midi_get_event_count #define jack_midi_event_get WJACK_midi_event_get #define jack_midi_event_write WJACK_midi_event_write #define jack_midi_clear_buffer WJACK_midi_clear_buffer /* */ #define jack_set_session_callback WJACK_set_session_callback #define jack_session_reply WJACK_session_reply #define jack_session_event_free WJACK_session_event_free /* */ #define jack_ringbuffer_create WJACK_ringbuffer_create #define jack_ringbuffer_free WJACK_ringbuffer_free #define jack_ringbuffer_reset WJACK_ringbuffer_reset #define jack_ringbuffer_read_advance WJACK_ringbuffer_read_advance #define jack_ringbuffer_write_advance WJACK_ringbuffer_write_advance #define jack_ringbuffer_read_space WJACK_ringbuffer_read_space #define jack_ringbuffer_write_space WJACK_ringbuffer_write_space #define jack_ringbuffer_read WJACK_ringbuffer_read #define jack_ringbuffer_write WJACK_ringbuffer_write #define jack_ringbuffer_mlock WJACK_ringbuffer_mlock #define jack_ringbuffer_get_read_vector WJACK_ringbuffer_get_read_vector #define jack_ringbuffer_get_write_vector WJACK_ringbuffer_get_write_vector #define jack_ringbuffer_peek WJACK_ringbuffer_peek /* */ #define jack_client_real_time_priority WJACK_client_real_time_priority #define jack_client_max_real_time_priority WJACK_client_max_real_time_priority #define jack_acquire_real_time_scheduling WJACK_acquire_real_time_scheduling #define jack_client_create_thread WJACK_client_create_thread #define jack_drop_real_time_scheduling WJACK_drop_real_time_scheduling #define jack_client_stop_thread WJACK_client_stop_thread #define jack_client_kill_thread WJACK_client_kill_thread #define jack_set_thread_creator WJACK_set_thread_creator #define jack_client_open WJACK_client_client_openXXX #ifndef NO_JACK_METADATA /* */ #define jack_get_uuid_for_client_name WJACK_get_uuid_for_client_name #define jack_get_client_name_by_uuid WJACK_get_client_name_by_uuid #define jack_port_uuid WJACK_port_uuid #define jack_set_property WJACK_set_property #define jack_get_property WJACK_get_property #define jack_free_description WJACK_free_description #define jack_get_properties WJACK_get_properties #define jack_get_all_properties WJACK_get_all_properties #define jack_remove_property WJACK_remove_property #define jack_remove_properties WJACK_remove_properties #define jack_remove_all_properties WJACK_remove_all_properties #define jack_set_property_change_callback WJACK_set_property_change_callback #endif /* */ #define jack_get_max_delayed_usecs WJACK_get_max_delayed_usecs #define jack_get_xrun_delayed_usecs WJACK_get_xrun_delayed_usecs #define jack_reset_max_delayed_usecs WJACK_reset_max_delayed_usecs #endif // end USE_WEAK_JACK #include #include #include #include #include #include #ifndef NO_JACK_METADATA #include #endif #ifdef USE_WEAK_JACK #undef jack_client_open /* var-args hack */ #ifdef __cplusplus extern "C" { #endif void (* WJACK_get_client_open (void)) (void); jack_client_t * WJACK_no_client_open (const char *client_name, jack_options_t options, jack_status_t *status, ...); #ifdef __cplusplus } #endif #define jack_client_open(...) \ ( \ (WJACK_get_client_open() != NULL) \ ? ((jack_client_t* (*)(const char *, jack_options_t, jack_status_t *, ...))(WJACK_get_client_open()))(__VA_ARGS__) \ : WJACK_no_client_open(__VA_ARGS__) \ ) #endif // end USE_WEAK_JACK #endif // _WEAK_JACK_H robtk-0.8.5/widgets/000077500000000000000000000000001463170413500143175ustar00rootroot00000000000000robtk-0.8.5/widgets/robtk_checkbutton.h000066400000000000000000000350731463170413500202120ustar00rootroot00000000000000/* radio button widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_CBTN_H_ #define _ROB_TK_CBTN_H_ #define GBT_LED_RADIUS (11.0) enum GedLedMode { GBT_LED_RADIO = -2, GBT_LED_LEFT = -1, GBT_LED_OFF = 0, GBT_LED_RIGHT = 1 }; typedef struct { RobWidget* rw; bool sensitive; bool prelight; bool enabled; enum GedLedMode show_led; bool flat_button; bool radiomode; int temporary_mode; bool (*cb) (RobWidget* w, void* handle); void* handle; void (*touch_cb) (void*, uint32_t, bool); void* touch_hd; uint32_t touch_id; void (*ttip) (RobWidget* rw, bool on, void* handle); void* ttip_handle; cairo_pattern_t* btn_enabled; cairo_pattern_t* btn_inactive; cairo_pattern_t* btn_led; cairo_surface_t* sf_txt_normal; cairo_surface_t* sf_txt_enabled; char *txt; float scale; float w_width, w_height, l_width, l_height; float c_on[4]; float coff[4]; float c_ck[4]; pthread_mutex_t _mutex; } RobTkCBtn; /****************************************************************************** * some helpers */ static void robtk_cbtn_update_enabled(RobTkCBtn * d, bool enabled) { if (enabled != d->enabled) { d->enabled = enabled; if (d->cb) d->cb(d->rw, d->handle); queue_draw(d->rw); } } static void create_cbtn_pattern(RobTkCBtn * d) { float c_bg[4]; get_color_from_theme(1, c_bg); if (d->btn_inactive) cairo_pattern_destroy(d->btn_inactive); if (d->btn_enabled) cairo_pattern_destroy(d->btn_enabled); if (d->btn_led) cairo_pattern_destroy(d->btn_led); d->btn_inactive = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, 1.95)); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 0.75)); d->btn_enabled = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); if (d->show_led == GBT_LED_OFF) { cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(d->c_ck) ? 0.5 : 0.0, SHADE_RGB(d->c_ck, 0.5)); cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(d->c_ck) ? 0.0 : 0.5, SHADE_RGB(d->c_ck, 1.0)); } else { cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, .95)); cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 2.4)); } d->btn_led = cairo_pattern_create_linear (0.0, 0.0, 0.0, GBT_LED_RADIUS); cairo_pattern_add_color_stop_rgba (d->btn_led, 0.0, 0.0, 0.0, 0.0, 0.4); cairo_pattern_add_color_stop_rgba (d->btn_led, 1.0, 1.0, 1.0, 1.0, 0.7); } static void create_cbtn_text_surface (RobTkCBtn* d) { float c_col[4]; get_color_from_theme(0, c_col); pthread_mutex_lock (&d->_mutex); PangoFontDescription *font = get_font_from_theme(); d->scale = d->rw->widget_scale; create_text_surface3s (&d->sf_txt_normal, d->l_width, d->l_height, d->l_width / 2.0, d->l_height / 2.0, d->txt, font, c_col, d->rw->widget_scale * RTK_SCALE_MUL); /* sf_txt_enabled, is used with btn_enabled surface, see create_cbtn_pattern() */ bool bright_text; if (d->show_led == GBT_LED_OFF) { /*btn_enabled c_ck is shaded by .5 .. 1.0 */ bright_text = luminance_rgb (d->c_ck) < .6; } else { /*btn_enabled c_bg is brighted by .95 .. 2.4*/ get_color_from_theme(1, c_col); bright_text = luminance_rgb (c_col) < .21; } if (bright_text) { c_col[0] = 1.f; c_col[1] = 1.f; c_col[2] = 1.f; c_col[3] = 1.f; } else { c_col[0] = 0; c_col[1] = 0; c_col[2] = 0; c_col[3] = 1.f; } create_text_surface3s (&d->sf_txt_enabled, d->l_width, d->l_height, d->l_width / 2.0, d->l_height / 2.0, d->txt, font, c_col, d->rw->widget_scale * RTK_SCALE_MUL); pango_font_description_free(font); pthread_mutex_unlock (&d->_mutex); } /****************************************************************************** * main drawing callback */ static bool robtk_cbtn_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle); if (d->scale != d->rw->widget_scale) { create_cbtn_text_surface(d); } if (pthread_mutex_trylock (&d->_mutex)) { queue_draw(d->rw); return TRUE; } cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); float led_r, led_g, led_b; // TODO consolidate with c[] cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale); float c[4]; get_color_from_theme(1, c); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (!d->sensitive) { led_r = c[0]; led_g = c[1]; led_b = c[2]; } else if (d->enabled) { if (d->radiomode) { led_r = .3; led_g = .8; led_b = .1; } else { led_r = d->c_on[0]; led_g = d->c_on[1]; led_b = d->c_on[2]; } } else { if (d->radiomode) { led_r = .1; led_g = .3; led_b = .1; } else { led_r = d->coff[0]; led_g = d->coff[1]; led_b = d->coff[2]; } } if (!d->flat_button) { if (d->enabled) { cairo_set_source(cr, d->btn_enabled); } else if (!d->sensitive) { cairo_set_source_rgb (cr, c[0], c[1], c[2]); } else { cairo_set_source(cr, d->btn_inactive); } rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve (cr); if (!d->sensitive && d->enabled) { cairo_set_source_rgba (cr, c[0], c[1], c[2], .6); cairo_fill_preserve (cr); } cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } else { cairo_set_source_rgb (cr, c[0], c[1], c[2]); rounded_rectangle(cr, 2, 2, d->w_width - 3, d->w_height - 3, C_RAD); cairo_fill(cr); } const float lspace = d->w_width - d->l_width - (d->show_led ? GBT_LED_RADIUS + 6 : 0); const float lhpad = d->show_led < 0 ? GBT_LED_RADIUS + 6 : 0; const float xalign = rint((lhpad + lspace * d->rw->xalign) * d->scale); const float yalign = rint((d->w_height - d->l_height) * d->rw->yalign * d->scale); cairo_save (cr); cairo_scale (cr, RTK_SCALE_DIV / d->rw->widget_scale, RTK_SCALE_DIV / d->rw->widget_scale); if (d->flat_button && !d->sensitive) { cairo_set_operator (cr, CAIRO_OPERATOR_EXCLUSION); cairo_set_source_surface(cr, d->sf_txt_normal, xalign, yalign); } else if (!d->flat_button && d->enabled) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_surface(cr, d->sf_txt_enabled, xalign, yalign); } else { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_surface(cr, d->sf_txt_normal, xalign, yalign); } cairo_paint (cr); cairo_restore (cr); if (d->show_led) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_save(cr); if (d->show_led == GBT_LED_LEFT || d->show_led == GBT_LED_RADIO) { cairo_translate(cr, GBT_LED_RADIUS/2 + 7, d->w_height/2.0 + 1); } else { cairo_translate(cr, d->w_width - GBT_LED_RADIUS/2 - 7, d->w_height/2.0 + 1); } cairo_set_source (cr, d->btn_led); cairo_arc (cr, 0, 0, GBT_LED_RADIUS/2, 0, 2 * M_PI); cairo_fill(cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_arc (cr, 0, 0, GBT_LED_RADIUS/2 - 2, 0, 2 * M_PI); cairo_fill(cr); cairo_set_source_rgba (cr, led_r, led_g, led_b, 1.0); cairo_arc (cr, 0, 0, GBT_LED_RADIUS/2 - 3, 0, 2 * M_PI); cairo_fill(cr); cairo_restore(cr); } if (d->sensitive && d->prelight) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (ISBRIGHT(c)) { cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .1); } else { cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); } if (d->flat_button) { rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill(cr); } else { rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve(cr); cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } } pthread_mutex_unlock (&d->_mutex); return TRUE; } /****************************************************************************** * UI callbacks */ static RobWidget* robtk_cbtn_mousedown(RobWidget *handle, RobTkBtnEvent *event) { RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!d->prelight) { return NULL; } if (d->radiomode && d->enabled) { return NULL; } if (d->touch_cb && event->button == 1) { d->touch_cb (d->touch_hd, d->touch_id, true); } if (d->ttip) { d->ttip (d->rw, false, d->ttip_handle); } if ( ((d->temporary_mode & 1) && event->button == 3) || ((d->temporary_mode & 2) && event->state & ROBTK_MOD_SHIFT) || ((d->temporary_mode & 4) && event->state & ROBTK_MOD_CTRL) ) { robtk_cbtn_update_enabled(d, ! d->enabled); } return NULL; } static RobWidget* robtk_cbtn_mouseup(RobWidget *handle, RobTkBtnEvent *event) { RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (d->radiomode && d->enabled) { return NULL; } if (event->button !=1 && !((d->temporary_mode & 1) && event->button == 3)) { return NULL; } if (d->prelight) { robtk_cbtn_update_enabled(d, ! d->enabled); } if (d->touch_cb && event->button == 1) { d->touch_cb (d->touch_hd, d->touch_id, false); } return NULL; } static void robtk_cbtn_enter_notify(RobWidget *handle) { RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } if (d->ttip) { d->ttip (d->rw, true, d->ttip_handle); } } static void robtk_cbtn_leave_notify(RobWidget *handle) { RobTkCBtn * d = (RobTkCBtn *)GET_HANDLE(handle); if (d->prelight) { d->prelight = FALSE; queue_draw(d->rw); } if (d->ttip) { d->ttip (d->rw, false, d->ttip_handle); } } /****************************************************************************** * RobWidget stuff */ static void priv_cbtn_size_request(RobWidget* handle, int *w, int *h) { RobTkCBtn* d = (RobTkCBtn*)GET_HANDLE(handle); *w = d->l_width * d->rw->widget_scale; *h = d->l_height * d->rw->widget_scale; } static void priv_cbtn_size_allocate(RobWidget* handle, int w, int h) { RobTkCBtn* d = (RobTkCBtn*)GET_HANDLE(handle); bool recreate_patterns = FALSE; if (h != d->w_height * d->rw->widget_scale) recreate_patterns = TRUE; if (w != d->w_width * d->rw->widget_scale) d->scale = 0; // re-layout d->w_width = w / d->rw->widget_scale; d->w_height = h / d->rw->widget_scale; if (recreate_patterns) { d->scale = 0; create_cbtn_pattern(d); } robwidget_set_size(handle, w, h); } /****************************************************************************** * public functions */ static RobTkCBtn * robtk_cbtn_new(const char* txt, enum GedLedMode led, bool flat) { assert(txt); RobTkCBtn *d = (RobTkCBtn *) calloc(1, sizeof(RobTkCBtn)); d->flat_button = flat; d->show_led = led; d->cb = NULL; d->handle = NULL; d->touch_cb = NULL; d->touch_hd = NULL; d->touch_id = 0; d->ttip = NULL; d->ttip_handle = NULL; d->sensitive = TRUE; d->radiomode = FALSE; d->temporary_mode = 0; d->prelight = FALSE; d->enabled = FALSE; d->txt = strdup(txt); d->scale = 1.0; pthread_mutex_init (&d->_mutex, 0); d->c_on[0] = .8; d->c_on[1] = .3; d->c_on[2] = .1; d->c_on[3] = 1.0; d->coff[0] = .3; d->coff[1] = .1; d->coff[2] = .1; d->coff[3] = 1.0; d->c_ck[0] = .2; d->c_ck[1] = .7; d->c_ck[2] = .22; d->c_ck[3] = 1.0; if (led == GBT_LED_RADIO) { d->radiomode = TRUE; } int ww, wh; PangoFontDescription *fd = get_font_from_theme(); get_text_geometry(txt, fd, &ww, &wh); pango_font_description_free(fd); assert(d->show_led || ww > 0); d->w_width = ((ww > 0) ? (ww + 14) : 7) + (d->show_led ? GBT_LED_RADIUS + 6 : 0); d->w_height = wh + 8; d->l_width = d->w_width; d->l_height = d->w_height; d->rw = robwidget_new(d); create_cbtn_text_surface(d); robwidget_set_alignment(d->rw, .5, .5); ROBWIDGET_SETNAME(d->rw, "cbtn"); robwidget_set_size_request(d->rw, priv_cbtn_size_request); robwidget_set_size_allocate(d->rw, priv_cbtn_size_allocate); robwidget_set_expose_event(d->rw, robtk_cbtn_expose_event); robwidget_set_mousedown(d->rw, robtk_cbtn_mousedown); robwidget_set_mouseup(d->rw, robtk_cbtn_mouseup); robwidget_set_enter_notify(d->rw, robtk_cbtn_enter_notify); robwidget_set_leave_notify(d->rw, robtk_cbtn_leave_notify); create_cbtn_pattern(d); return d; } static void robtk_cbtn_destroy(RobTkCBtn *d) { robwidget_destroy(d->rw); cairo_pattern_destroy(d->btn_enabled); cairo_pattern_destroy(d->btn_inactive); cairo_pattern_destroy(d->btn_led); cairo_surface_destroy(d->sf_txt_normal); cairo_surface_destroy(d->sf_txt_enabled); pthread_mutex_destroy(&d->_mutex); free(d->txt); free(d); } static void robtk_cbtn_set_alignment(RobTkCBtn *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_cbtn_widget(RobTkCBtn *d) { return d->rw; } static void robtk_cbtn_set_callback(RobTkCBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_cbtn_set_touch(RobTkCBtn *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) { d->touch_cb = cb; d->touch_hd = handle; d->touch_id = id; } static void robtk_cbtn_annotation_callback(RobTkCBtn *d, void (*cb) (RobWidget* w, bool, void* handle), void* handle) { d->ttip = cb; d->ttip_handle = handle; } static void robtk_cbtn_set_active(RobTkCBtn *d, bool v) { robtk_cbtn_update_enabled(d, v); } static void robtk_cbtn_set_sensitive(RobTkCBtn *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static void robtk_cbtn_set_text(RobTkCBtn *d, const char *txt) { free (d->txt); d->txt = strdup (txt); create_cbtn_text_surface (d); queue_draw (d->rw); } static void robtk_cbtn_set_color_on(RobTkCBtn *d, float r, float g, float b) { d->c_on[0] = r; d->c_on[1] = g; d->c_on[2] = b; } static void robtk_cbtn_set_color_off(RobTkCBtn *d, float r, float g, float b) { d->coff[0] = r; d->coff[1] = g; d->coff[2] = b; } static void robtk_cbtn_set_color_checked(RobTkCBtn *d, float r, float g, float b) { d->c_ck[0] = r; d->c_ck[1] = g; d->c_ck[2] = b; create_cbtn_pattern (d); } static void robtk_cbtn_set_temporary_mode(RobTkCBtn *d, int i) { if (d->radiomode) return; d->temporary_mode = i; } static bool robtk_cbtn_get_active(RobTkCBtn *d) { return (d->enabled); } #endif robtk-0.8.5/widgets/robtk_checkimgbutton.h000066400000000000000000000211421463170413500206770ustar00rootroot00000000000000/* radio button widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_CIMGBTN_H_ #define _ROB_TK_CIMGBTN_H_ typedef struct { RobWidget* rw; bool sensitive; bool prelight; bool enabled; int temporary_mode; bool (*cb) (RobWidget* w, void* handle); void* handle; void (*touch_cb) (void*, uint32_t, bool); void* touch_hd; uint32_t touch_id; cairo_pattern_t* btn_enabled; cairo_pattern_t* btn_inactive; cairo_surface_t* sf_img_normal; cairo_surface_t* sf_img_enabled; float w_width, w_height, i_width, i_height; float scale; } RobTkIBtn; /****************************************************************************** * some helpers */ static void robtk_ibtn_update_enabled(RobTkIBtn * d, bool enabled) { if (enabled != d->enabled) { d->enabled = enabled; if (d->cb) d->cb(d->rw, d->handle); queue_draw(d->rw); } } static void create_ibtn_pattern(RobTkIBtn * d) { float c_bg[4]; get_color_from_theme(1, c_bg); if (d->btn_inactive) cairo_pattern_destroy(d->btn_inactive); if (d->btn_enabled) cairo_pattern_destroy(d->btn_enabled); d->btn_inactive = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, 1.95)); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 0.75)); d->btn_enabled = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, .95)); cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 2.4)); } /****************************************************************************** * main drawing callback */ static bool robtk_ibtn_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkIBtn * d = (RobTkIBtn *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); float c[4]; get_color_from_theme(1, c); cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (d->enabled) { cairo_set_source(cr, d->btn_enabled); } else if (!d->sensitive) { cairo_set_source_rgb (cr, c[0], c[1], c[2]); } else { cairo_set_source(cr, d->btn_inactive); } rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve (cr); if (!d->sensitive && d->enabled) { cairo_set_source_rgba (cr, c[0], c[1], c[2], .6); cairo_fill_preserve (cr); } cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); const float xalign = 5 + rint((d->w_width - 9 - d->i_width) * d->rw->xalign); const float yalign = 5 + rint((d->w_height - 9 - d->i_height) * d->rw->yalign); cairo_save (cr); cairo_scale (cr, d->scale, d->scale); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (d->enabled) { cairo_set_source_surface(cr, d->sf_img_enabled, xalign, yalign); } else { cairo_set_source_surface(cr, d->sf_img_normal, xalign, yalign); } cairo_paint (cr); cairo_restore (cr); if (d->sensitive && d->prelight) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (ISBRIGHT(c)) { cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .1); } else { cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); } rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve(cr); cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } return TRUE; } /****************************************************************************** * UI callbacks */ static RobWidget* robtk_ibtn_mousedown(RobWidget *handle, RobTkBtnEvent *event) { RobTkIBtn * d = (RobTkIBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!d->prelight) { return NULL; } if (d->touch_cb && event->button == 1) { d->touch_cb (d->touch_hd, d->touch_id, true); } if ( ((d->temporary_mode & 1) && event->button == 3) || ((d->temporary_mode & 2) && event->state & ROBTK_MOD_SHIFT) || ((d->temporary_mode & 4) && event->state & ROBTK_MOD_CTRL) ) { robtk_ibtn_update_enabled(d, ! d->enabled); } return NULL; } static RobWidget* robtk_ibtn_mouseup(RobWidget *handle, RobTkBtnEvent *event) { RobTkIBtn * d = (RobTkIBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (event->button !=1 && !((d->temporary_mode & 1) && event->button == 3)) { return NULL; } if (d->prelight) { robtk_ibtn_update_enabled(d, ! d->enabled); } if (d->touch_cb && event->button == 1) { d->touch_cb (d->touch_hd, d->touch_id, false); } return NULL; } static void robtk_ibtn_enter_notify(RobWidget *handle) { RobTkIBtn * d = (RobTkIBtn *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } } static void robtk_ibtn_leave_notify(RobWidget *handle) { RobTkIBtn * d = (RobTkIBtn *)GET_HANDLE(handle); if (d->prelight) { d->prelight = FALSE; queue_draw(d->rw); } } /****************************************************************************** * RobWidget stuff */ static void priv_ibtn_size_request(RobWidget* handle, int *w, int *h) { RobTkIBtn* d = (RobTkIBtn*)GET_HANDLE(handle); *w = (d->i_width + 9) * d->rw->widget_scale; *h = (d->i_height + 9) * d->rw->widget_scale; } static void priv_ibtn_size_allocate(RobWidget* handle, int w, int h) { RobTkIBtn* d = (RobTkIBtn*)GET_HANDLE(handle); bool recreate_patterns = FALSE; if (h != d->w_height * d->rw->widget_scale) recreate_patterns = TRUE; d->w_width = w / d->rw->widget_scale; d->w_height = h / d->rw->widget_scale; if (recreate_patterns) create_ibtn_pattern(d); robwidget_set_size(handle, w, h); } /****************************************************************************** * public functions */ static RobTkIBtn * robtk_ibtn_new(cairo_surface_t *n, cairo_surface_t *e, float scale) { RobTkIBtn *d = (RobTkIBtn *) malloc(sizeof(RobTkIBtn)); d->cb = NULL; d->handle = NULL; d->touch_cb = NULL; d->touch_hd = NULL; d->touch_id = 0; d->sf_img_normal = n; d->sf_img_enabled = e; d->btn_enabled = NULL; d->btn_inactive = NULL; d->temporary_mode = 0; d->sensitive = TRUE; d->prelight = FALSE; d->enabled = FALSE; d->scale = 1.0 / scale; d->i_width = cairo_image_surface_get_width(n); d->i_height = cairo_image_surface_get_height(n); d->w_width = d->i_width + 9; d->w_height = d->i_height + 9; d->rw = robwidget_new(d); robwidget_set_alignment(d->rw, .5, .5); ROBWIDGET_SETNAME(d->rw, "ibtn"); robwidget_set_size_request(d->rw, priv_ibtn_size_request); robwidget_set_size_allocate(d->rw, priv_ibtn_size_allocate); robwidget_set_expose_event(d->rw, robtk_ibtn_expose_event); robwidget_set_mousedown(d->rw, robtk_ibtn_mousedown); robwidget_set_mouseup(d->rw, robtk_ibtn_mouseup); robwidget_set_enter_notify(d->rw, robtk_ibtn_enter_notify); robwidget_set_leave_notify(d->rw, robtk_ibtn_leave_notify); create_ibtn_pattern(d); return d; } static void robtk_ibtn_destroy(RobTkIBtn *d) { robwidget_destroy(d->rw); cairo_pattern_destroy(d->btn_enabled); cairo_pattern_destroy(d->btn_inactive); free(d); } static void robtk_ibtn_set_alignment(RobTkIBtn *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_ibtn_widget(RobTkIBtn *d) { return d->rw; } static void robtk_ibtn_set_callback(RobTkIBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_ibtn_set_touch(RobTkIBtn *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) { d->touch_cb = cb; d->touch_hd = handle; d->touch_id = id; } static void robtk_ibtn_set_active(RobTkIBtn *d, bool v) { robtk_ibtn_update_enabled(d, v); } static void robtk_ibtn_set_sensitive(RobTkIBtn *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static void robtk_ibtn_set_temporary_mode(RobTkIBtn *d, int i) { d->temporary_mode = i; } static bool robtk_ibtn_get_active(RobTkIBtn *d) { return (d->enabled); } #endif robtk-0.8.5/widgets/robtk_dial.h000066400000000000000000000517021463170413500166070ustar00rootroot00000000000000/* dial widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_DIAL_H_ #define _ROB_TK_DIAL_H_ /* default values used by robtk_dial_new() * for calling robtk_dial_new_with_size() */ #define GED_WIDTH 55 #define GED_HEIGHT 30 #define GED_RADIUS 10 #define GED_CX 27.5 #define GED_CY 12.5 typedef struct _RobTkDial { RobWidget *rw; float min; float max; float acc; float cur; float dfl; float alt; float base_mult; float scroll_mult; float dead_zone_delta; int n_detents; float *detent; bool constrain_to_accuracy; int click_state; int click_states; int click_dflt; float scroll_accel; #define ACCEL_THRESH 10 struct timespec scroll_accel_timeout; int scroll_accel_thresh; bool with_scroll_accel; float drag_x, drag_y, drag_c; bool dragging; bool clicking; bool sensitive; bool prelight; int displaymode; bool (*cb) (RobWidget* w, void* handle); void* handle; void (*ann) (struct _RobTkDial* d, cairo_t *cr, void* handle); void* ann_handle; void (*touch_cb) (void*, uint32_t, bool); void* touch_hd; uint32_t touch_id; bool touching; cairo_pattern_t* dpat; cairo_surface_t* bg; float bg_scale; float w_width, w_height; float w_cx, w_cy; float w_radius; float *scol; float dcol[4][4]; bool threesixty; } RobTkDial; static bool robtk_dial_expose_event (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale); float c[4]; get_color_from_theme(1, c); cairo_set_source_rgb (cr, c[0], c[1], c[2]); if ((d->displaymode & 16) == 0) { cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_rectangle (cr, 0, 0, d->w_width, d->w_height); cairo_fill(cr); } if (d->bg) { if (!d->sensitive) { cairo_set_operator (cr, CAIRO_OPERATOR_SOFT_LIGHT); } else { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); } cairo_save (cr); cairo_scale (cr, d->bg_scale, d->bg_scale); cairo_set_source_surface(cr, d->bg, 0, 0); cairo_paint (cr); cairo_restore (cr); cairo_set_source_rgb (cr, c[0], c[1], c[2]); } cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (d->sensitive) { cairo_set_source(cr, d->dpat); } cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius, 0, 2.0 * M_PI); cairo_fill_preserve (cr); cairo_set_line_width(cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke (cr); if (d->sensitive && d->click_state > 0) { CairoSetSouerceRGBA(&d->scol[4*(d->click_state-1)]); cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius-1, 0, 2.0 * M_PI); cairo_fill(cr); } if (d->sensitive) { CairoSetSouerceRGBA(d->dcol[0]); } else { CairoSetSouerceRGBA(d->dcol[1]); } float ang; if (d->threesixty) { ang = (.5 * M_PI) + (2.0 * M_PI) * (d->cur - d->min) / (d->max - d->min); } else { ang = (.75 * M_PI) + (1.5 * M_PI) * (d->cur - d->min) / (d->max - d->min); } if ((d->displaymode & 1) == 0) { /* line from center */ cairo_set_line_width(cr, 1.5); cairo_move_to(cr, d->w_cx, d->w_cy); float wid = M_PI * 2 / 180.0; cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius, ang-wid, ang+wid); cairo_stroke (cr); } else { /* dot */ cairo_save(cr); cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND); cairo_translate(cr, d->w_cx, d->w_cy); cairo_rotate (cr, ang); cairo_set_line_width(cr, 3.5); cairo_move_to(cr, d->w_radius - 5.0, 0); cairo_close_path(cr); cairo_stroke (cr); if (d->displaymode & 2) { /* small shade in dot */ cairo_set_source_rgba (cr, .2, .2, .2, .1); cairo_set_line_width(cr, 1.5); cairo_move_to(cr, d->w_radius - 4.75, 0); cairo_close_path(cr); cairo_stroke (cr); } cairo_restore(cr); } if ((d->displaymode & 4) && !d->threesixty) { cairo_set_line_width(cr, 1.5); CairoSetSouerceRGBA(d->dcol[3]); cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius + 1.5, (.75 * M_PI), (2.25 * M_PI)); cairo_stroke (cr); if (d->sensitive) { CairoSetSouerceRGBA(d->dcol[2]); } else { CairoSetSouerceRGBA(d->dcol[3]); } if (d->displaymode & 8) { float dfl = (.75 * M_PI) + (1.5 * M_PI) * (d->dfl - d->min) / (d->max - d->min); if (dfl < ang) { cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius + 1.5, dfl, ang); cairo_stroke (cr); } else if (dfl > ang) { cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius + 1.5, ang, dfl); cairo_stroke (cr); } } else { cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius + 1.5, (.75 * M_PI), ang); cairo_stroke (cr); } } if (d->sensitive && (d->prelight || d->dragging)) { if (ISBRIGHT(c)) { cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .15); } else { cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .15); } cairo_arc (cr, d->w_cx, d->w_cy, d->w_radius-1, 0, 2.0 * M_PI); cairo_fill(cr); if (d->ann) d->ann(d, cr, d->ann_handle); } return TRUE; } static void robtk_dial_update_state(RobTkDial * d, int state) { if (state < 0) state = 0; if (state > d->click_states) state = d->click_states; if (state != d->click_state) { d->click_state = state; if (d->cb) d->cb(d->rw, d->handle); queue_draw(d->rw); } } static void robtk_dial_update_value(RobTkDial * d, float val) { if (d->threesixty) { // TODO use fmod(), but retain accuracy constraint while (val < d->min) val += (d->max - d->min); while (val > d->max) val -= (d->max - d->min); assert (val >= d->min && val <= d->max); } else { if (val < d->min) val = d->min; if (val > d->max) val = d->max; } if (d->constrain_to_accuracy) { val = d->min + rintf((val-d->min) / d->acc ) * d->acc; } if (val != d->cur) { d->cur = val; if (d->cb) d->cb(d->rw, d->handle); queue_draw(d->rw); } } static RobWidget* robtk_dial_mousedown(RobWidget* handle, RobTkBtnEvent *ev) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, true); } if (ev->state & ROBTK_MOD_SHIFT) { robtk_dial_update_value(d, d->dfl); robtk_dial_update_state(d, d->click_dflt); } else if (ev->button == 3) { if (d->cur == d->dfl) { robtk_dial_update_value(d, d->alt); } else { d->alt = d->cur; robtk_dial_update_value(d, d->dfl); } } else if (ev->button == 1) { d->dragging = TRUE; d->clicking = TRUE; d->drag_x = ev->x; d->drag_y = ev->y; d->drag_c = d->cur; } queue_draw(d->rw); return handle; } static RobWidget* robtk_dial_mouseup(RobWidget* handle, RobTkBtnEvent *ev) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); if (!d->sensitive) { d->dragging = FALSE; d->clicking = FALSE; return NULL; } d->dragging = FALSE; if (d->clicking) { robtk_dial_update_state(d, (d->click_state + 1) % (d->click_states + 1)); } d->clicking = FALSE; if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, false); } queue_draw(d->rw); return NULL; } static RobWidget* robtk_dial_mousemove(RobWidget* handle, RobTkBtnEvent *ev) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); if (!d->dragging) return NULL; d->clicking = FALSE; if (!d->sensitive) { d->dragging = FALSE; queue_draw(d->rw); return NULL; } #ifndef ABSOLUTE_DRAGGING const float mult = (ev->state & ROBTK_MOD_CTRL) ? (d->base_mult * 0.1) : d->base_mult; #else const float mult = d->base_mult; #endif float diff = ((ev->x - d->drag_x) - (ev->y - d->drag_y)); if (diff == 0) { return handle; } #define DRANGE(X) (!d->threesixty ? X : (d->min + fmod ((X) - d->min, (d->max - d->min)))) for (int i = 0; i < d->n_detents; ++i) { const float px_deadzone = 34.f - d->n_detents; // px if (DRANGE(d->cur - d->detent[i]) * DRANGE(d->cur - d->detent[i] + diff * mult) < 0) { /* detent */ const int tozero = DRANGE(d->cur - d->detent[i]) * mult; int remain = diff - tozero; if (abs (remain) > px_deadzone) { /* slow down passing the default value */ remain += (remain > 0) ? px_deadzone * -.5 : px_deadzone * .5; diff = tozero + remain; d->dead_zone_delta = 0; } else { robtk_dial_update_value(d, d->detent[i]); d->dead_zone_delta = remain / px_deadzone; d->drag_x = ev->x; d->drag_y = ev->y; goto out; } } if (fabsf (rintf(DRANGE(d->cur - d->detent[i]) / mult) + d->dead_zone_delta) < 1) { robtk_dial_update_value(d, d->detent[i]); d->dead_zone_delta += diff / px_deadzone; d->drag_x = ev->x; d->drag_y = ev->y; goto out; } } if (d->constrain_to_accuracy) { diff = rintf(diff * mult * (d->max - d->min) / d->acc ) * d->acc; } else { diff *= mult; } if (diff != 0) { d->dead_zone_delta = 0; } robtk_dial_update_value(d, d->drag_c + diff); out: #ifndef ABSOLUTE_DRAGGING if (d->drag_c != d->cur) { d->drag_x = ev->x; d->drag_y = ev->y; d->drag_c = d->cur; } #endif return handle; } static void robtk_dial_enter_notify(RobWidget *handle) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } } static void robtk_dial_leave_notify(RobWidget *handle) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); if (d->touch_cb && d->touching) { d->touch_cb (d->touch_hd, d->touch_id, false); d->touching = FALSE; } if (d->prelight) { d->prelight = FALSE; d->scroll_accel = 1.0; d->scroll_accel_thresh = 0; queue_draw(d->rw); } } static RobWidget* robtk_dial_scroll(RobWidget* handle, RobTkBtnEvent *ev) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (d->dragging) { d->dragging = FALSE; } if (d->with_scroll_accel) { struct timespec now; rtk_clock_gettime(&now); int64_t ts0 = now.tv_sec * 1000 + now.tv_nsec / 1000000; int64_t ts1 = d->scroll_accel_timeout.tv_sec * 1000 + d->scroll_accel_timeout.tv_nsec / 1000000; if (ts0 - ts1 < 100) { if (abs(d->scroll_accel_thresh) > ACCEL_THRESH && d->scroll_accel < 4) { d->scroll_accel += .025; } } else { d->scroll_accel_thresh = 0; d->scroll_accel = 1.0; } d->scroll_accel_timeout.tv_sec = now.tv_sec; d->scroll_accel_timeout.tv_nsec = now.tv_nsec; } else { d->scroll_accel_thresh = 0; d->scroll_accel = 1.0; } const float delta = (ev->state & ROBTK_MOD_CTRL) ? d->acc : d->scroll_mult * d->acc; float val = d->cur; switch (ev->direction) { case ROBTK_SCROLL_RIGHT: case ROBTK_SCROLL_UP: if (d->scroll_accel_thresh < 0) { d->scroll_accel_thresh = 0; d->scroll_accel = 1.0; } else if (d->scroll_accel_thresh <= ACCEL_THRESH) { d->scroll_accel_thresh++; } val += delta * d->scroll_accel; break; case ROBTK_SCROLL_LEFT: case ROBTK_SCROLL_DOWN: if (d->scroll_accel_thresh > 0) { d->scroll_accel_thresh = 0; d->scroll_accel = 1.0; } else if (d->scroll_accel_thresh >= -ACCEL_THRESH) { d->scroll_accel_thresh--; } val -= delta * d->scroll_accel; break; default: break; } if (d->touch_cb && !d->touching) { d->touch_cb (d->touch_hd, d->touch_id, true); d->touching = TRUE; } robtk_dial_update_value(d, val); return NULL; } static void create_dial_pattern(RobTkDial * d, const float c_bg[4]) { if (d->dpat) { cairo_pattern_destroy(d->dpat); } cairo_pattern_t* pat = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); const float pat_left = (d->w_cx - d->w_radius) / (float) d->w_width; const float pat_right = (d->w_cx + d->w_radius) / (float) d->w_width; const float pat_top = (d->w_cy - d->w_radius) / (float) d->w_height; const float pat_bottom = (d->w_cy + d->w_radius) / (float) d->w_height; #define PAT_XOFF(VAL) (pat_left + 0.35 * 2.0 * d->w_radius) if (ISBRIGHT(c_bg)) { cairo_pattern_add_color_stop_rgb (pat, pat_top, SHADE_RGB(c_bg, .95)); cairo_pattern_add_color_stop_rgb (pat, pat_bottom, SHADE_RGB(c_bg, 2.4)); } else { cairo_pattern_add_color_stop_rgb (pat, pat_top, SHADE_RGB(c_bg, 2.4)); cairo_pattern_add_color_stop_rgb (pat, pat_bottom, SHADE_RGB(c_bg, .95)); } if (!getenv("NO_METER_SHADE") || strlen(getenv("NO_METER_SHADE")) == 0) { /* light from top-left */ cairo_pattern_t* shade_pattern = cairo_pattern_create_linear (0.0, 0.0, d->w_width, 0.0); if (ISBRIGHT(c_bg)) { cairo_pattern_add_color_stop_rgba (shade_pattern, pat_left, 1.0, 1.0, 1.0, 0.15); cairo_pattern_add_color_stop_rgba (shade_pattern, PAT_XOFF(0.35), 0.0, 0.0, 0.0, 0.10); cairo_pattern_add_color_stop_rgba (shade_pattern, PAT_XOFF(0.53), 1.0, 1.0, 1.0, 0.05); cairo_pattern_add_color_stop_rgba (shade_pattern, pat_right, 1.0, 1.0, 1.0, 0.25); } else { cairo_pattern_add_color_stop_rgba (shade_pattern, pat_left, 0.0, 0.0, 0.0, 0.15); cairo_pattern_add_color_stop_rgba (shade_pattern, PAT_XOFF(0.35), 1.0, 1.0, 1.0, 0.10); cairo_pattern_add_color_stop_rgba (shade_pattern, PAT_XOFF(0.53), 0.0, 0.0, 0.0, 0.05); cairo_pattern_add_color_stop_rgba (shade_pattern, pat_right, 0.0, 0.0, 0.0, 0.25); } cairo_surface_t* surface; cairo_t* tc = 0; surface = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, d->w_width, d->w_height); tc = cairo_create (surface); cairo_set_operator (tc, CAIRO_OPERATOR_SOURCE); cairo_set_source (tc, pat); cairo_rectangle (tc, 0, 0, d->w_width, d->w_height); cairo_fill (tc); cairo_pattern_destroy (pat); cairo_set_operator (tc, CAIRO_OPERATOR_OVER); cairo_set_source (tc, shade_pattern); cairo_rectangle (tc, 0, 0, d->w_width, d->w_height); cairo_fill (tc); cairo_pattern_destroy (shade_pattern); pat = cairo_pattern_create_for_surface (surface); cairo_destroy (tc); cairo_surface_destroy (surface); } d->dpat = pat; } /****************************************************************************** * RobWidget stuff */ static void robtk_dial_size_request(RobWidget* handle, int *w, int *h) { RobTkDial * d = (RobTkDial *)GET_HANDLE(handle); *w = d->w_width * d->rw->widget_scale; *h = d->w_height * d->rw->widget_scale; } /****************************************************************************** * public functions */ static RobTkDial * robtk_dial_new_with_size(float min, float max, float step, int width, int height, float cx, float cy, float radius) { assert(max > min); assert(step > 0); //assert( (max - min) / step <= 2048.0); // TODO if > 250, use acceleration, mult assert( (max - min) / step >= 1.0); assert( (cx + radius) < width); assert( (cx - radius) > 0); assert( (cy + radius) < height); assert( (cy - radius) > 0); RobTkDial *d = (RobTkDial *) malloc(sizeof(RobTkDial)); d->w_width = width; d->w_height = height; d->w_cx = cx; d->w_cy = cy; d->w_radius = radius; d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "dial"); robwidget_set_expose_event(d->rw, robtk_dial_expose_event); robwidget_set_size_request(d->rw, robtk_dial_size_request); robwidget_set_mouseup(d->rw, robtk_dial_mouseup); robwidget_set_mousedown(d->rw, robtk_dial_mousedown); robwidget_set_mousemove(d->rw, robtk_dial_mousemove); robwidget_set_mousescroll(d->rw, robtk_dial_scroll); robwidget_set_enter_notify(d->rw, robtk_dial_enter_notify); robwidget_set_leave_notify(d->rw, robtk_dial_leave_notify); d->cb = NULL; d->handle = NULL; d->ann = NULL; d->ann_handle = NULL; d->touch_cb = NULL; d->touch_hd = NULL; d->touch_id = 0; d->touching = FALSE; d->min = min; d->max = max; d->acc = step; d->cur = min; d->dfl = min; d->alt = min; d->n_detents = 0; d->detent = NULL; d->constrain_to_accuracy = TRUE; d->dead_zone_delta = 0; d->sensitive = TRUE; d->prelight = FALSE; d->dragging = FALSE; d->clicking = FALSE; d->threesixty = FALSE; d->displaymode = 0; d->click_states = 0; d->click_state = 0; d->click_dflt = 0; d->drag_x = d->drag_y = 0; d->scroll_accel = 1.0; d->base_mult = (((d->max - d->min) / d->acc) < 12) ? (d->acc * 12.0 / (d->max - d->min)) : 1.0; d->base_mult *= 0.004; // 250px d->scroll_mult = 1.0; d->scroll_accel_thresh = 0; d->with_scroll_accel = true; rtk_clock_gettime(&d->scroll_accel_timeout); d->bg = NULL; d->dpat = NULL; d->bg_scale = 1.0; float c_bg[4]; get_color_from_theme(1, c_bg); create_dial_pattern(d, c_bg); d->scol = (float*) malloc(3 * 4 * sizeof(float)); d->scol[0*4] = 1.0; d->scol[0*4+1] = 0.0; d->scol[0*4+2] = 0.0; d->scol[0*4+3] = 0.2; d->scol[1*4] = 0.0; d->scol[1*4+1] = 1.0; d->scol[1*4+2] = 0.0; d->scol[1*4+3] = 0.2; d->scol[2*4] = 0.0; d->scol[2*4+1] = 0.0; d->scol[2*4+2] = 1.0; d->scol[2*4+3] = 0.25; float c[4]; get_color_from_theme(1, c); if (ISBRIGHT(c)) { d->dcol[0][0] = .05; d->dcol[0][1] = .05; d->dcol[0][2] = .05; d->dcol[0][3] = 1.0; d->dcol[1][0] = .45; d->dcol[1][1] = .45; d->dcol[1][2] = .45; d->dcol[1][3] = 0.7; } else { d->dcol[0][0] = .95; d->dcol[0][1] = .95; d->dcol[0][2] = .95; d->dcol[0][3] = 1.0; d->dcol[1][0] = .55; d->dcol[1][1] = .55; d->dcol[1][2] = .55; d->dcol[1][3] = 0.7; } d->dcol[2][0] = .0; d->dcol[2][1] = .75; d->dcol[2][2] = 1.0; d->dcol[2][3] = 0.8; d->dcol[3][0] = .50; d->dcol[3][1] = .50; d->dcol[3][2] = .50; d->dcol[3][3] = 0.5; return d; } static RobTkDial * robtk_dial_new(float min, float max, float step) { return robtk_dial_new_with_size(min, max, step, GED_WIDTH, GED_HEIGHT, GED_CX, GED_CY, GED_RADIUS); } static void robtk_dial_destroy(RobTkDial *d) { robwidget_destroy(d->rw); cairo_pattern_destroy(d->dpat); free(d->scol); free(d->detent); free(d); } static void robtk_dial_set_alignment(RobTkDial *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_dial_widget(RobTkDial *d) { return d->rw; } static void robtk_dial_set_callback(RobTkDial *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_dial_annotation_callback(RobTkDial *d, void (*cb) (RobTkDial* d, cairo_t *cr, void* handle), void* handle) { d->ann = cb; d->ann_handle = handle; } static void robtk_dial_set_touch(RobTkDial *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) { d->touch_cb = cb; d->touch_hd = handle; d->touch_id = id; } static void robtk_dial_set_default(RobTkDial *d, float v) { if (d->constrain_to_accuracy) { v = d->min + rintf((v-d->min) / d->acc ) * d->acc; } assert(v >= d->min); assert(v <= d->max); d->dfl = v; d->alt = v; } static void robtk_dial_set_value(RobTkDial *d, float v) { robtk_dial_update_value(d, v); } static void robtk_dial_set_sensitive(RobTkDial *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static float robtk_dial_get_value(RobTkDial *d) { return (d->cur); } static void robtk_dial_enable_states(RobTkDial *d, int s) { if (s < 0) s = 0; if (s > 3) s = 3; // TODO realloc d->scol, allow N states d->click_states = s; robtk_dial_update_state(d, d->click_state % (d->click_states + 1)); } static void robtk_dial_set_state(RobTkDial *d, int s) { robtk_dial_update_state(d, s); } static int robtk_dial_get_state(RobTkDial *d) { return (d->click_state); } static void robtk_dial_set_default_state(RobTkDial *d, int s) { assert(s >= 0); assert(s <= d->click_states); d->click_dflt = s; } static void robtk_dial_set_state_color(RobTkDial *d, int s, float r, float g, float b, float a) { assert(s > 0); assert(s <= d->click_states); d->scol[(s-1)*4+0] = r; d->scol[(s-1)*4+1] = g; d->scol[(s-1)*4+2] = b; d->scol[(s-1)*4+3] = a; if (d->click_state == s) { queue_draw(d->rw); } } static void robtk_dial_set_scroll_mult(RobTkDial *d, float v) { d->scroll_mult = v; } static void robtk_dial_set_detents(RobTkDial *d, const int n, const float *p) { free(d->detent); assert (n < 15); // XXX d->n_detents = n; d->detent = (float*) malloc(n * sizeof(float)); memcpy(d->detent, p, n * sizeof(float)); } static void robtk_dial_set_detent_default(RobTkDial *d, bool v) { free(d->detent); d->n_detents = 1; d->detent = (float*) malloc(sizeof(float)); d->detent[0] = d->dfl; } static void robtk_dial_set_constained(RobTkDial *d, bool v) { d->constrain_to_accuracy = v; } static void robtk_dial_set_scaled_surface_scale(RobTkDial* d, cairo_surface_t* b, const float s) { d->bg = b; d->bg_scale = 1.0 / s; } static void robtk_dial_set_surface(RobTkDial *d, cairo_surface_t *s) { d->bg = s; d->bg_scale = 1.0; } static bool robtk_dial_update_range (RobTkDial *d, float min, float max, float step) { if (max <= min || step <= 0) return FALSE; if ((max - min) / step < 1.0) return FALSE; //assert( (max - min) / step <= 2048.0); d->min = min; d->max = max; d->acc = step; d->base_mult = (((d->max - d->min) / d->acc) < 12) ? (d->acc * 12.0 / (d->max - d->min)) : 1.0; d->base_mult *= 0.004; if (d->dfl < min) d->dfl = min; if (d->dfl > max) d->dfl = max; robtk_dial_update_value(d, d->cur); return TRUE; } #endif robtk-0.8.5/widgets/robtk_drawingarea.h000066400000000000000000000046211463170413500201600ustar00rootroot00000000000000/* Image widget * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_DAREA_H_ #define _ROB_TK_DAREA_H_ typedef struct { RobWidget *rw; float w_width, w_height; void (*expose) (cairo_t* cr, void *d); void* handle; } RobTkDarea; static bool robtk_darea_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkDarea* d = (RobTkDarea *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); if (d->expose) d->expose(cr, d->handle); return TRUE; } /****************************************************************************** * RobWidget stuff */ static void priv_darea_size_request(RobWidget* handle, int *w, int *h) { RobTkDarea* d = (RobTkDarea*)GET_HANDLE(handle); *w = d->w_width; *h = d->w_height; } /****************************************************************************** * public functions */ static RobTkDarea * robtk_darea_new(const unsigned int w, const unsigned int h, void (*expose) (cairo_t* cr, void *d), void *handle) { RobTkDarea *d = (RobTkDarea *) malloc(sizeof(RobTkDarea)); d->w_width = w; d->w_height = h; d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "DArea"); robwidget_set_expose_event(d->rw, robtk_darea_expose_event); robwidget_set_size_request(d->rw, priv_darea_size_request); d->expose = expose; d->handle = handle; return d; } static void robtk_darea_destroy(RobTkDarea *d) { robwidget_destroy(d->rw); free(d); } static void robtk_darea_set_alignment(RobTkDarea *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_darea_widget(RobTkDarea *d) { return d->rw; } static void robtk_darea_redraw(RobTkDarea *d) { queue_draw(d->rw); } #endif robtk-0.8.5/widgets/robtk_image.h000066400000000000000000000062021463170413500167530ustar00rootroot00000000000000/* Image widget * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_IMAGE_H_ #define _ROB_TK_IMAGE_H_ typedef struct { RobWidget *rw; float w_width, w_height; uint8_t *img_data; cairo_surface_t *img_surface; } RobTkImg; static bool robtk_img_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkImg* d = (RobTkImg *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_set_source_surface(cr, d->img_surface, 0, 0); cairo_paint(cr); return TRUE; } /****************************************************************************** * RobWidget stuff */ static void priv_img_size_request(RobWidget* handle, int *w, int *h) { RobTkImg* d = (RobTkImg*)GET_HANDLE(handle); *w = d->w_width; *h = d->w_height; } /****************************************************************************** * public functions */ static RobTkImg * robtk_img_new(const unsigned int w, const unsigned int h, const unsigned bpp, const uint8_t * const img) { RobTkImg *d = (RobTkImg *) malloc(sizeof(RobTkImg)); assert(bpp == 3 || bpp == 4); d->w_width = w; d->w_height = h; unsigned int x,y; int stride = cairo_format_stride_for_width (CAIRO_FORMAT_ARGB32, w); d->img_data = (unsigned char *) malloc (stride * h); d->img_surface = cairo_image_surface_create_for_data(d->img_data, CAIRO_FORMAT_ARGB32, w, h, stride); cairo_surface_flush (d->img_surface); for (y = 0; y < h; ++y) { const int y0 = y * stride; const int ys = y * w * bpp; for (x = 0; x < w; ++x) { const int xs = x * bpp; const int xd = x * 4; if (bpp == 3) { d->img_data[y0 + xd + 3] = 0xff; } else { d->img_data[y0 + xd + 3] = img[ys + xs + 3]; // A } d->img_data[y0 + xd + 2] = img[ys + xs]; // R d->img_data[y0 + xd + 1] = img[ys + xs + 1]; // G d->img_data[y0 + xd + 0] = img[ys + xs + 2]; // B } } cairo_surface_mark_dirty (d->img_surface); d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "Image"); robwidget_set_expose_event(d->rw, robtk_img_expose_event); robwidget_set_size_request(d->rw, priv_img_size_request); return d; } static void robtk_img_destroy(RobTkImg *d) { robwidget_destroy(d->rw); cairo_surface_destroy(d->img_surface); free(d->img_data); free(d); } static void robtk_img_set_alignment(RobTkImg *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_img_widget(RobTkImg *d) { return d->rw; } #endif robtk-0.8.5/widgets/robtk_label.h000066400000000000000000000162651463170413500167620ustar00rootroot00000000000000/* label widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_LBL_H_ #define _ROB_TK_LBL_H_ typedef struct _RobTkLbl { RobWidget *rw; bool sensitive; cairo_surface_t* sf_txt; float w_width, w_height; float min_width, min_width_scaled; float min_height, min_height_scaled; char *txt; char *fontdesc; float fg[4]; float bg[4]; bool rounded; pthread_mutex_t _mutex; float scale; void (*ttip) (RobWidget* rw, bool on, void* handle); void* ttip_handle; } RobTkLbl; static void priv_lbl_prepare_text(RobTkLbl *d, const char *txt) { // _mutex must be held to call this function int ww, wh; PangoFontDescription *fd; if (d->fontdesc) { fd = pango_font_description_from_string(d->fontdesc); } else { fd = get_font_from_theme(); } get_text_geometry(txt, fd, &ww, &wh); d->w_width = ww + 4; d->w_height = wh + 4; if (d->scale != d->rw->widget_scale) { d->min_width_scaled = d->min_width * d->rw->widget_scale; d->min_height_scaled = d->min_height * d->rw->widget_scale; } d->w_width = ceil (d->w_width * d->rw->widget_scale); d->w_height = ceil (d->w_height * d->rw->widget_scale); d->scale = d->rw->widget_scale; if (d->w_width < d->min_width_scaled) d->w_width = d->min_width_scaled; if (d->w_height < d->min_height_scaled) d->w_height = d->min_height_scaled; #ifndef GTK_BACKEND // never shrink for given scale - no jitter if (d->w_width > d->min_width_scaled) d->min_width_scaled = d->w_width; if (d->w_height > d->min_height_scaled) d->min_height_scaled = d->w_height; #elif 0 // resize window|widget robwidget_hide(d->rw, false); robwidget_show(d->rw, true); #endif create_text_surface3 (&d->sf_txt, RTK_SCALE_MUL * d->w_width, RTK_SCALE_MUL * d->w_height, ceil (RTK_SCALE_MUL * d->w_width / 2.0) + 1, ceil (RTK_SCALE_MUL * d->w_height / 2.0) + 1, txt, fd, d->fg, d->rw->widget_scale * RTK_SCALE_MUL); pango_font_description_free(fd); robwidget_set_size(d->rw, d->w_width, d->w_height); // TODO trigger re-layout resize_self() queue_tiny_area(d->rw, 0, 0, d->w_width, d->w_height); } static bool robtk_lbl_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkLbl* d = (RobTkLbl *)GET_HANDLE(handle); if (pthread_mutex_trylock (&d->_mutex)) { queue_draw(d->rw); return TRUE; } if (d->scale != d->rw->widget_scale) { priv_lbl_prepare_text(d, d->txt); } cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_set_source_rgba (cr, d->bg[0], d->bg[1], d->bg[2], d->bg[3]); if (!d->rounded) { cairo_rectangle (cr, 0, 0, d->w_width, d->w_height); cairo_fill(cr); } else { rounded_rectangle(cr, 0.5, 0.5, d->w_width - 1, d->w_height - 1, C_RAD); cairo_fill_preserve(cr); cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } if (d->sensitive) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); } else { cairo_set_operator (cr, CAIRO_OPERATOR_EXCLUSION); } cairo_save (cr); cairo_scale (cr, RTK_SCALE_DIV, RTK_SCALE_DIV); cairo_set_source_surface(cr, d->sf_txt, 0, 0); cairo_paint (cr); cairo_restore (cr); pthread_mutex_unlock (&d->_mutex); return TRUE; } /****************************************************************************** * RobWidget stuff */ static void priv_lbl_size_request(RobWidget* handle, int *w, int *h) { RobTkLbl* d = (RobTkLbl*)GET_HANDLE(handle); if (d->rw->widget_scale != d->scale) { pthread_mutex_lock (&d->_mutex); priv_lbl_prepare_text(d, d->txt); pthread_mutex_unlock (&d->_mutex); } *w = d->w_width; *h = d->w_height; } /****************************************************************************** * public functions */ static void robtk_lbl_set_text(RobTkLbl *d, const char *txt) { assert(txt); pthread_mutex_lock (&d->_mutex); free(d->txt); d->txt = strdup(txt); priv_lbl_prepare_text(d, d->txt); pthread_mutex_unlock (&d->_mutex); } static RobTkLbl * robtk_lbl_new(const char * txt) { assert(txt); RobTkLbl *d = (RobTkLbl *) malloc(sizeof(RobTkLbl)); d->sf_txt = NULL; d->min_width_scaled = d->min_width = d->w_width = 0; d->min_height_scaled = d->min_height = d->w_height = 0; d->txt = NULL; d->fontdesc = NULL; d->sensitive = TRUE; d->rounded = FALSE; d->scale = 1.0; d->ttip = NULL; d->ttip_handle = NULL; pthread_mutex_init (&d->_mutex, 0); d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "label"); robwidget_set_expose_event(d->rw, robtk_lbl_expose_event); robwidget_set_size_request(d->rw, priv_lbl_size_request); get_color_from_theme(1, d->bg); get_color_from_theme(0, d->fg); robtk_lbl_set_text(d, txt); return d; } static void robtk_lbl_destroy(RobTkLbl *d) { robwidget_destroy(d->rw); pthread_mutex_destroy(&d->_mutex); cairo_surface_destroy(d->sf_txt); free(d->txt); free(d->fontdesc); free(d); } static void robtk_lbl_set_alignment(RobTkLbl *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static void robtk_lbl_set_min_geometry(RobTkLbl *d, float w, float h) { d->min_width = w; d->min_height = h; assert(d->txt); pthread_mutex_lock (&d->_mutex); priv_lbl_prepare_text(d, d->txt); pthread_mutex_unlock (&d->_mutex); } static RobWidget * robtk_lbl_widget(RobTkLbl *d) { return d->rw; } static void robtk_lbl_set_sensitive(RobTkLbl *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static void robtk_lbl_ttip_show (RobWidget *handle) { RobTkLbl* d = (RobTkLbl *)GET_HANDLE(handle); if (d->ttip) { d->ttip (d->rw, true, d->ttip_handle); } } static void robtk_lbl_ttip_hide (RobWidget *handle) { RobTkLbl* d = (RobTkLbl *)GET_HANDLE(handle); if (d->ttip) { d->ttip (d->rw, false, d->ttip_handle); } } static void robtk_lbl_annotation_callback(RobTkLbl *d, void (*cb) (RobWidget* w, bool, void* handle), void* handle) { d->ttip = cb; d->ttip_handle = handle; if (d->ttip) { robwidget_set_enter_notify(d->rw, robtk_lbl_ttip_show); robwidget_set_leave_notify(d->rw, robtk_lbl_ttip_hide); } else { robwidget_set_enter_notify(d->rw, NULL); robwidget_set_leave_notify(d->rw, NULL); } } static void robtk_lbl_set_color(RobTkLbl *d, float r, float g, float b, float a) { d->fg[0] = r; d->fg[1] = g; d->fg[2] = b; d->fg[3] = a; assert(d->txt); pthread_mutex_lock (&d->_mutex); priv_lbl_prepare_text(d, d->txt); pthread_mutex_unlock (&d->_mutex); } static void robtk_lbl_set_fontdesc(RobTkLbl *d, const char *fontdesc) { free(d->fontdesc); d->fontdesc = strdup(fontdesc); assert(d->txt); pthread_mutex_lock (&d->_mutex); priv_lbl_prepare_text(d, d->txt); pthread_mutex_unlock (&d->_mutex); } #endif robtk-0.8.5/widgets/robtk_multibutton.h000066400000000000000000000210571463170413500202640ustar00rootroot00000000000000/* radio button widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_MBTN_H_ #define _ROB_TK_MBTN_H_ #define MBT_LED_RADIUS (11.0) typedef struct { RobWidget* rw; bool sensitive; bool prelight; bool (*cb) (RobWidget* w, void* handle); void* handle; int num_mode; int cur_mode; int tog_mode; int dfl_mode; cairo_pattern_t* btn_enabled; cairo_pattern_t* btn_inactive; cairo_pattern_t* btn_led; float w_width, w_height; float *c_led; } RobTkMBtn; /****************************************************************************** * some helpers */ static void robtk_mbtn_update_mode(RobTkMBtn * d, int mode) { if (mode != d->cur_mode && mode >=0 && mode <= d->num_mode) { d->cur_mode = mode; if (d->cb) d->cb(d->rw, d->handle); queue_draw(d->rw); } } static void create_mbtn_pattern(RobTkMBtn * d) { float c_bg[4]; get_color_from_theme(1, c_bg); d->btn_inactive = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, 1.95)); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 0.75)); d->btn_enabled = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, .95)); cairo_pattern_add_color_stop_rgb (d->btn_enabled, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 2.4)); d->btn_led = cairo_pattern_create_linear (0.0, 0.0, 0.0, MBT_LED_RADIUS); cairo_pattern_add_color_stop_rgba (d->btn_led, 0.0, 0.0, 0.0, 0.0, 0.4); cairo_pattern_add_color_stop_rgba (d->btn_led, 1.0, 1.0, 1.0, 1.0 , 0.7); } /****************************************************************************** * main drawing callback */ static bool robtk_mbtn_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkMBtn * d = (RobTkMBtn *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale); float led_r, led_g, led_b; // TODO consolidate with c[] cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); float c[4]; get_color_from_theme(1, c); cairo_set_source_rgb (cr, c[0], c[1], c[2]); cairo_rectangle (cr, 0, 0, d->w_width, d->w_height); cairo_fill(cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (!d->sensitive) { led_r = c[0]; led_g = c[1]; led_b = c[2]; } else { const int m = d->cur_mode; led_r = d->c_led[m*3+0]; led_g = d->c_led[m*3+1]; led_b = d->c_led[m*3+2]; } if (d->cur_mode > 0) { cairo_set_source(cr, d->btn_enabled); } else if (!d->sensitive) { cairo_set_source_rgb (cr, c[0], c[1], c[2]); } else { cairo_set_source(cr, d->btn_inactive); } rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve (cr); if (!d->sensitive && d->cur_mode > 0) { cairo_set_source_rgba (cr, c[0], c[1], c[2], .6); cairo_fill_preserve (cr); } cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_save(cr); cairo_translate(cr, MBT_LED_RADIUS/2 + 7, d->w_height/2.0 + 1); cairo_set_source (cr, d->btn_led); cairo_arc (cr, 0, 0, MBT_LED_RADIUS/2, 0, 2 * M_PI); cairo_fill(cr); cairo_set_source_rgb (cr, 0, 0, 0); cairo_arc (cr, 0, 0, MBT_LED_RADIUS/2 - 2, 0, 2 * M_PI); cairo_fill(cr); cairo_set_source_rgba (cr, led_r, led_g, led_b, 1.0); cairo_arc (cr, 0, 0, MBT_LED_RADIUS/2 - 3, 0, 2 * M_PI); cairo_fill(cr); cairo_restore(cr); if (d->sensitive && d->prelight) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (ISBRIGHT(c)) { cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .1); } else { cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); } rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve(cr); cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } return TRUE; } /****************************************************************************** * UI callbacks */ static RobWidget* robtk_mbtn_mouseup(RobWidget *handle, RobTkBtnEvent *ev) { RobTkMBtn * d = (RobTkMBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!d->prelight) { return NULL; } if (ev->state & ROBTK_MOD_SHIFT) { robtk_mbtn_update_mode(d, d->dfl_mode); } else if (ev->state & ROBTK_MOD_CTRL) { int cur = d->cur_mode; robtk_mbtn_update_mode(d, d->tog_mode); d->tog_mode = cur; } else { robtk_mbtn_update_mode(d, (d->cur_mode + 1) % d->num_mode); } return NULL; } static void robtk_mbtn_enter_notify(RobWidget *handle) { RobTkMBtn * d = (RobTkMBtn *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } } static void robtk_mbtn_leave_notify(RobWidget *handle) { RobTkMBtn * d = (RobTkMBtn *)GET_HANDLE(handle); if (d->prelight) { d->prelight = FALSE; queue_draw(d->rw); } } /****************************************************************************** * RobWidget stuff */ static void priv_mbtn_size_request(RobWidget* handle, int *w, int *h) { RobTkMBtn* d = (RobTkMBtn*)GET_HANDLE(handle); *w = d->w_width * d->rw->widget_scale; *h = d->w_height * d->rw->widget_scale; } /****************************************************************************** * public functions */ static RobTkMBtn * robtk_mbtn_new(int modes) { RobTkMBtn *d = (RobTkMBtn *) malloc(sizeof(RobTkMBtn)); assert(modes > 1); d->cb = NULL; d->handle = NULL; d->sensitive = TRUE; d->prelight = FALSE; d->num_mode = modes; d->cur_mode = 0; d->tog_mode = 0; d->dfl_mode = 0; d->c_led = (float*) malloc(3 * modes * sizeof(float)); d->c_led[0] = d->c_led[1] = d->c_led[2] = .4; for (int c = 1; c < modes; ++c) { const float hue = /* (.0 / (modes-1.f)) + */ (c-1.f) / (modes-1.f) ; const float sat = .95; const float lum = .55; const float cq = lum < 0.5 ? lum * (1 + sat) : lum + sat - lum * sat; const float cp = 2.f * lum - cq; d->c_led[c*3+0] = rtk_hue2rgb(cp, cq, hue + 1.f/3.f); d->c_led[c*3+1] = rtk_hue2rgb(cp, cq, hue); d->c_led[c*3+2] = rtk_hue2rgb(cp, cq, hue - 1.f/3.f); //printf("h %d %.3f -> %.3f %.3f %.3f\n", c, hue, d->c_led[c*3+0], d->c_led[c*3+1], d->c_led[c*3+2]); } int ww, wh; PangoFontDescription *fd = get_font_from_theme(); get_text_geometry("", fd, &ww, &wh); pango_font_description_free(fd); d->w_width = 13 + MBT_LED_RADIUS; d->w_height = wh + 8; d->rw = robwidget_new(d); robwidget_set_alignment(d->rw, 0, .5); ROBWIDGET_SETNAME(d->rw, "mbtn"); robwidget_set_size_request(d->rw, priv_mbtn_size_request); robwidget_set_expose_event(d->rw, robtk_mbtn_expose_event); robwidget_set_mouseup(d->rw, robtk_mbtn_mouseup); robwidget_set_enter_notify(d->rw, robtk_mbtn_enter_notify); robwidget_set_leave_notify(d->rw, robtk_mbtn_leave_notify); create_mbtn_pattern(d); return d; } static void robtk_mbtn_destroy(RobTkMBtn *d) { robwidget_destroy(d->rw); cairo_pattern_destroy(d->btn_enabled); cairo_pattern_destroy(d->btn_inactive); cairo_pattern_destroy(d->btn_led); free(d->c_led); free(d); } static void robtk_mbtn_set_alignment(RobTkMBtn *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_mbtn_widget(RobTkMBtn *d) { return d->rw; } static void robtk_mbtn_set_callback(RobTkMBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_mbtn_set_leds_rgb(RobTkMBtn *d, const float *c) { memcpy(d->c_led, c, d->num_mode * 3 * sizeof(float)); } static void robtk_mbtn_set_default(RobTkMBtn *d, int v) { d->dfl_mode = v; } static void robtk_mbtn_set_active(RobTkMBtn *d, int v) { robtk_mbtn_update_mode(d, v); } static void robtk_mbtn_set_sensitive(RobTkMBtn *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static int robtk_mbtn_get_active(RobTkMBtn *d) { return (d->cur_mode); } #endif robtk-0.8.5/widgets/robtk_pushbutton.h000066400000000000000000000227161463170413500201140ustar00rootroot00000000000000/* push button widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_PBTN_H_ #define _ROB_TK_PBTN_H_ typedef struct { RobWidget* rw; bool sensitive; bool prelight; bool enabled; bool (*cb) (RobWidget* w, void* handle); void* handle; bool (*cb_up) (RobWidget* w, void* handle); void* handle_up; bool (*cb_down) (RobWidget* w, void* handle); void* handle_down; cairo_pattern_t* btn_active; cairo_pattern_t* btn_inactive; cairo_surface_t* sf_txt; char *txt; float scale; float w_width, w_height, l_width, l_height; float fg[4]; float bg[4]; pthread_mutex_t _mutex; } RobTkPBtn; static void create_pbtn_text_surface (RobTkPBtn* d) { PangoFontDescription *font = get_font_from_theme(); pthread_mutex_lock (&d->_mutex); d->scale = d->rw->widget_scale; create_text_surface3s (&d->sf_txt, d->l_width, d->l_height, d->l_width / 2.0, d->l_height / 2.0, d->txt, font, d->fg, d->rw->widget_scale * RTK_SCALE_MUL); pthread_mutex_unlock (&d->_mutex); pango_font_description_free(font); } static bool robtk_pbtn_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); if (d->scale != d->rw->widget_scale) { create_pbtn_text_surface (d); } if (pthread_mutex_trylock (&d->_mutex)) { queue_draw(d->rw); return TRUE; } cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); if (!d->sensitive) { cairo_set_source_rgb (cr, d->bg[0], d->bg[1], d->bg[2]); } else if (d->enabled) { cairo_set_source(cr, d->btn_active); } else { cairo_set_source(cr, d->btn_inactive); } rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve (cr); cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); if (d->enabled) { cairo_set_operator (cr, CAIRO_OPERATOR_EXCLUSION); } else { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); } const float xalign = rint((d->w_width - d->l_width) * d->rw->xalign * d->scale); const float yalign = rint((d->w_height - d->l_height) * d->rw->yalign * d->scale); cairo_save (cr); cairo_scale (cr, RTK_SCALE_DIV / d->rw->widget_scale, RTK_SCALE_DIV / d->rw->widget_scale); cairo_set_source_surface(cr, d->sf_txt, xalign, yalign); cairo_paint (cr); cairo_restore (cr); if (d->sensitive && d->prelight) { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill_preserve(cr); cairo_set_line_width (cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } pthread_mutex_unlock (&d->_mutex); return TRUE; } /****************************************************************************** * UI callbacks */ static RobWidget* robtk_pbtn_mousedown(RobWidget *handle, RobTkBtnEvent *event) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!d->prelight) { return NULL; } d->enabled = TRUE; if (d->cb_down) d->cb_down(d->rw, d->handle_down); queue_draw(d->rw); return NULL; } static RobWidget* robtk_pbtn_mouseup(RobWidget *handle, RobTkBtnEvent *event) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (d->enabled && d->cb_up) { d->cb_up(d->rw, d->handle_up); } if (d->prelight && d->enabled) { if (d->cb) d->cb(d->rw, d->handle); } d->enabled = FALSE; queue_draw(d->rw); return NULL; } static void robtk_pbtn_enter_notify(RobWidget *handle) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } } static void robtk_pbtn_leave_notify(RobWidget *handle) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); if (d->prelight || d->enabled) { if (d->prelight && d->enabled) { if (d->cb) d->cb(d->rw, d->handle); } d->enabled = FALSE; d->prelight = FALSE; queue_draw(d->rw); } } static void create_pbtn_pattern(RobTkPBtn * d) { pthread_mutex_lock (&d->_mutex); if (d->btn_active) cairo_pattern_destroy(d->btn_active); if (d->btn_inactive) cairo_pattern_destroy(d->btn_inactive); d->btn_inactive = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(d->bg) ? 0.5 : 0.0, SHADE_RGB(d->bg, 1.95)); cairo_pattern_add_color_stop_rgb (d->btn_inactive, ISBRIGHT(d->bg) ? 0.0 : 0.5, SHADE_RGB(d->bg, 0.75)); d->btn_active = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_active, ISBRIGHT(d->bg) ? 0.5 : 0.0, SHADE_RGB(d->bg, .95)); cairo_pattern_add_color_stop_rgb (d->btn_active, ISBRIGHT(d->bg) ? 0.0 : 0.5, SHADE_RGB(d->bg, 2.4)); pthread_mutex_unlock (&d->_mutex); } /****************************************************************************** * RobWidget stuff */ static void priv_pbtn_size_request(RobWidget* handle, int *w, int *h) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); *w = d->l_width * d->rw->widget_scale; *h = d->l_height * d->rw->widget_scale; } static void priv_pbtn_size_allocate(RobWidget* handle, int w, int h) { RobTkPBtn * d = (RobTkPBtn *)GET_HANDLE(handle); bool recreate_patterns = FALSE; if (h != d->w_height * d->rw->widget_scale) recreate_patterns = TRUE; if (w != d->w_width * d->rw->widget_scale) d->scale = 0; // re-layout d->w_width = w / d->rw->widget_scale; d->w_height = h / d->rw->widget_scale; if (recreate_patterns) { d->scale = 0; create_pbtn_pattern(d); } robwidget_set_size(handle, w, h); } /****************************************************************************** * public functions */ static RobTkPBtn * robtk_pbtn_new_with_colors(const char * txt, const float bg[4], const float fg[4]) { assert(txt); RobTkPBtn *d = (RobTkPBtn *) malloc(sizeof(RobTkPBtn)); d->cb = NULL; d->handle = NULL; d->cb_up = NULL; d->handle_up = NULL; d->cb_down = NULL; d->handle_down = NULL; d->sf_txt = NULL; d->sensitive = TRUE; d->prelight = FALSE; d->enabled = FALSE; pthread_mutex_init (&d->_mutex, 0); d->btn_active = NULL; d->btn_inactive = NULL; d->sf_txt = NULL; d->txt = strdup(txt); d->scale = 1.0; memcpy(d->bg, bg, 4 * sizeof(float)); memcpy(d->fg, fg, 4 * sizeof(float)); int ww, wh; PangoFontDescription *fd = get_font_from_theme(); get_text_geometry(txt, fd, &ww, &wh); pango_font_description_free(fd); d->w_width = ww + 14; d->w_height = wh + 8; d->l_width = d->w_width; d->l_height = d->w_height; d->rw = robwidget_new(d); create_pbtn_text_surface(d); ROBWIDGET_SETNAME(d->rw, "pbtn"); robwidget_set_alignment(d->rw, .5, .5); robwidget_set_size_request(d->rw, priv_pbtn_size_request); robwidget_set_size_allocate(d->rw, priv_pbtn_size_allocate); robwidget_set_expose_event(d->rw, robtk_pbtn_expose_event); robwidget_set_mouseup(d->rw, robtk_pbtn_mouseup); robwidget_set_mousedown(d->rw, robtk_pbtn_mousedown); robwidget_set_enter_notify(d->rw, robtk_pbtn_enter_notify); robwidget_set_leave_notify(d->rw, robtk_pbtn_leave_notify); create_pbtn_pattern(d); return d; } static RobTkPBtn * robtk_pbtn_new(const char * txt) { float fg[4]; float bg[4]; get_color_from_theme(0, fg); get_color_from_theme(1, bg); return robtk_pbtn_new_with_colors(txt, bg, fg); } static void robtk_pbtn_destroy(RobTkPBtn *d) { robwidget_destroy(d->rw); cairo_pattern_destroy(d->btn_active); cairo_pattern_destroy(d->btn_inactive); cairo_surface_destroy(d->sf_txt); pthread_mutex_destroy(&d->_mutex); free(d->txt); free(d); } static void robtk_pbtn_set_alignment(RobTkPBtn *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static RobWidget * robtk_pbtn_widget(RobTkPBtn *d) { return d->rw; } static void robtk_pbtn_set_callback(RobTkPBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_pbtn_set_callback_up(RobTkPBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb_up = cb; d->handle_up = handle; } static void robtk_pbtn_set_callback_down(RobTkPBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb_down = cb; d->handle_down = handle; } static void robtk_pbtn_set_sensitive(RobTkPBtn *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static void robtk_pbtn_set_text(RobTkPBtn *d, const char *txt) { free (d->txt); d->txt = strdup (txt); create_pbtn_text_surface (d); queue_draw (d->rw); } static void robtk_pbtn_set_bg(RobTkPBtn *d, float r, float g, float b, float a) { if (d->bg[0] == r && d->bg[1] == g && d->bg[2] == b && d->bg[3] == a) { return; } d->bg[0] = r; d->bg[1] = g; d->bg[2] = b; d->bg[3] = a; create_pbtn_pattern(d); queue_draw(d->rw); } static bool robtk_pbtn_get_pushed(RobTkPBtn *d) { return (d->enabled); } #endif robtk-0.8.5/widgets/robtk_radiobutton.h000066400000000000000000000077761463170413500202440ustar00rootroot00000000000000/* radio button widget * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_RBTN_H_ #define _ROB_TK_RBTN_H_ #include "robtk_checkbutton.h" typedef struct { RobTkCBtn *cbtn; void * cbtngrp; bool own_radiogrp; bool (*cb) (RobWidget* w, void* handle); void* handle; } RobTkRBtn; typedef struct { RobTkRBtn **btn; unsigned int cnt; pthread_mutex_t _mutex; } RobTkRadioGrp; static void btn_group_add_btn (RobTkRadioGrp *g, RobTkRBtn *btn) { pthread_mutex_lock (&g->_mutex); g->btn = (RobTkRBtn **) realloc(g->btn, (g->cnt + 1) * sizeof(RobTkRBtn*)); g->btn[g->cnt] = btn; g->cnt++; pthread_mutex_unlock (&g->_mutex); } static void btn_group_remove_btn (RobTkRadioGrp *g, RobTkRBtn *btn) { pthread_mutex_lock (&g->_mutex); bool found = FALSE; for (unsigned int i = 0; i < g->cnt; ++i) { if (g->btn[i] == btn) { found = TRUE; } if (found && i+1 < g->cnt) { g->btn[i] = g->btn[i+1]; } } g->cnt++; pthread_mutex_unlock (&g->_mutex); } static RobTkRadioGrp * btn_group_new() { RobTkRadioGrp *g = (RobTkRadioGrp*) malloc(sizeof(RobTkRadioGrp)); g->btn=NULL; g->cnt=0; pthread_mutex_init (&g->_mutex, 0); return g; } static void btn_group_destroy(RobTkRadioGrp *g) { pthread_mutex_destroy(&g->_mutex); free(g->btn); free(g); } static void btn_group_propagate_change (RobTkRadioGrp *g, RobTkRBtn *btn) { pthread_mutex_lock (&g->_mutex); for (unsigned int i = 0; i < g->cnt; ++i) { if (g->btn[i] == btn) continue; robtk_cbtn_set_active(g->btn[i]->cbtn, FALSE); } pthread_mutex_unlock (&g->_mutex); } static bool btn_group_cbtn_callback(RobWidget *w, void* handle) { RobTkRBtn *d = (RobTkRBtn *) handle; if (robtk_cbtn_get_active(d->cbtn)) { btn_group_propagate_change((RobTkRadioGrp*) d->cbtngrp, d); } if (d->cb) d->cb(robtk_cbtn_widget(d->cbtn), d->handle); return TRUE; } /****************************************************************************** * public functions */ static RobTkRBtn * robtk_rbtn_new(const char * txt, RobTkRadioGrp *group) { RobTkRBtn *d = (RobTkRBtn *) malloc(sizeof(RobTkRBtn)); d->cbtn = robtk_cbtn_new(txt, GBT_LED_RADIO, TRUE); d->cb = NULL; d->handle = NULL; if (!group) { d->own_radiogrp = TRUE; d->cbtngrp = (void*) btn_group_new(); } else { d->own_radiogrp = FALSE; d->cbtngrp = group; } btn_group_add_btn((RobTkRadioGrp*) d->cbtngrp, d); robtk_cbtn_set_callback(d->cbtn, btn_group_cbtn_callback, d); return d; } static void robtk_rbtn_destroy(RobTkRBtn *d) { if (d->own_radiogrp) { btn_group_destroy((RobTkRadioGrp*) d->cbtngrp); } robtk_cbtn_destroy(d->cbtn); free(d); } static void robtk_rbtn_set_alignment(RobTkRBtn *d, float x, float y) { robtk_cbtn_set_alignment(d->cbtn, x, y); } static RobWidget * robtk_rbtn_widget(RobTkRBtn *d) { return robtk_cbtn_widget(d->cbtn); } static RobTkRadioGrp * robtk_rbtn_group(RobTkRBtn *d) { assert(d->cbtngrp); return (RobTkRadioGrp*) d->cbtngrp; } static void robtk_rbtn_set_callback(RobTkRBtn *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_rbtn_set_active(RobTkRBtn *d, bool v) { robtk_cbtn_set_active(d->cbtn, v); } static void robtk_rbtn_set_sensitive(RobTkRBtn *d, bool s) { robtk_cbtn_set_sensitive(d->cbtn, s); } static bool robtk_rbtn_get_active(RobTkRBtn *d) { return robtk_cbtn_get_active(d->cbtn); } #endif robtk-0.8.5/widgets/robtk_scale.h000066400000000000000000000402611463170413500167630ustar00rootroot00000000000000/* scale/slider widget * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_SCALE_H_ #define _ROB_TK_SCALE_H_ /* default values used by robtk_scale_new() * for calling robtk_scale_new_with_size() */ #define GSC_LENGTH 250 #define GSC_GIRTH 18 typedef struct { RobWidget *rw; float min; float max; float acc; float cur; float dfl; float drag_x, drag_y, drag_c; bool sensitive; bool prelight; bool (*cb) (RobWidget* w, void* handle); void* handle; void (*touch_cb) (void*, uint32_t, bool); void* touch_hd; uint32_t touch_id; bool touching; cairo_pattern_t* dpat; cairo_pattern_t* fpat; cairo_surface_t* bg; float w_width, w_height; bool horiz; char **mark_txt; float *mark_val; int mark_cnt; bool mark_expose; PangoFontDescription *mark_font; float c_txt[4]; float mark_space; pthread_mutex_t _mutex; } RobTkScale; static int robtk_scale_round_length(RobTkScale * d, float val) { if (d->horiz) { return rint((d->w_width - 8) * (val - d->min) / (d->max - d->min)); } else { return rint((d->w_height - 8) * (1.0 - (val - d->min) / (d->max - d->min))); } } static void robtk_scale_update_value(RobTkScale * d, float val) { if (val < d->min) val = d->min; if (val > d->max) val = d->max; if (val != d->cur) { float oldval = d->cur; d->cur = val; if (d->cb) d->cb(d->rw, d->handle); if (robtk_scale_round_length(d, oldval) != robtk_scale_round_length(d, val)) { val = robtk_scale_round_length(d, val); oldval = robtk_scale_round_length(d, oldval); cairo_rectangle_t rect; if (oldval > val) { if (d->horiz) { rect.x = 1 + val; rect.width = 9 + oldval - val; rect.y = d->rw->widget_scale * d->mark_space + 5; rect.height = d->w_height - 9 - d->mark_space * d->rw->widget_scale; } else { rect.x = 5; rect.width = d->w_width - 9 - d->mark_space * d->rw->widget_scale; rect.y = 1 + val; rect.height = 9 + oldval - val; } } else { if (d->horiz) { rect.x = 1 + oldval; rect.width = 9 + val - oldval; rect.y = d->rw->widget_scale * d->mark_space + 5; rect.height = d->w_height - 9 - d->mark_space * d->rw->widget_scale; } else { rect.x = 5; rect.width = d->w_width - 9 - d->mark_space * d->rw->widget_scale; rect.y = 1 + oldval; rect.height = 9 + val - oldval; } } #ifdef GTK_BACKEND if (1 /* XXX is visible */) { queue_tiny_area(d->rw, rect.x, rect.y, rect.width, rect.height); } #else if (d->rw->cached_position) { queue_tiny_area(d->rw, rect.x, rect.y, rect.width, rect.height); } #endif } } } static RobWidget* robtk_scale_mousedown(RobWidget *handle, RobTkBtnEvent *event) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, true); } if (event->state & ROBTK_MOD_SHIFT) { robtk_scale_update_value(d, d->dfl); } else { d->drag_x = event->x; d->drag_y = event->y; d->drag_c = d->cur; } queue_draw(d->rw); return handle; } static RobWidget* robtk_scale_mouseup(RobWidget *handle, RobTkBtnEvent *event) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } d->drag_x = d->drag_y = -1; if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, false); } queue_draw(d->rw); return NULL; } static RobWidget* robtk_scale_mousemove(RobWidget *handle, RobTkBtnEvent *event) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (d->drag_x < 0 || d->drag_y < 0) return NULL; if (!d->sensitive) { d->drag_x = d->drag_y = -1; queue_draw(d->rw); return NULL; } float len; float diff; if (d->horiz) { len = d->w_width - 8; diff = (event->x - d->drag_x) / len; } else { len = d->w_height - 8; diff = (d->drag_y - event->y) / len; } diff = rint(diff * (d->max - d->min) / d->acc ) * d->acc; float val = d->drag_c + diff; /* snap to mark */ const int snc = robtk_scale_round_length(d, val); // lock ?! for (int i = 0; i < d->mark_cnt; ++i) { int sn = robtk_scale_round_length(d, d->mark_val[i]); if (abs(sn-snc) < 3) { val = d->mark_val[i]; break; } } robtk_scale_update_value(d, val); return handle; } static void robtk_scale_enter_notify(RobWidget *handle) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } } static void robtk_scale_leave_notify(RobWidget *handle) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (d->touch_cb && d->touching) { d->touch_cb (d->touch_hd, d->touch_id, false); d->touching = FALSE; } if (d->prelight) { d->prelight = FALSE; queue_draw(d->rw); } } static void robtk_scale_size_request(RobWidget* handle, int *w, int *h) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); int rw, rh; if (d->horiz) { rh = GSC_GIRTH + (d->mark_cnt > 0 ? d->mark_space : 0); rh *= d->rw->widget_scale; rw = 250; } else { rw = GSC_GIRTH + (d->mark_cnt > 0 ? d->mark_space : 0); rw *= d->rw->widget_scale; rh = 250; } *w = d->w_width = rw; *h = d->w_height = rh; } static void robtk_scale_size_allocate(RobWidget* handle, int w, int h) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (d->horiz) { d->w_width = w; d->w_height = d->rw->widget_scale * (GSC_GIRTH * + (d->mark_cnt > 0 ? d->mark_space : 0)); if (d->w_height > h) { d->w_height = h; } } else { d->w_height = h; d->w_width = d->rw->widget_scale * (GSC_GIRTH + (d->mark_cnt > 0 ? d->mark_space : 0)); if (d->w_width > w) { d->w_width = w; } } robwidget_set_size(handle, d->w_width, d->w_height); if (d->mark_cnt > 0) { d->mark_expose = TRUE; } } static RobWidget* robtk_scale_scroll(RobWidget *handle, RobTkBtnEvent *ev) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!(d->drag_x < 0 || d->drag_y < 0)) { d->drag_x = d->drag_y = -1; } float val = d->cur; switch (ev->direction) { case ROBTK_SCROLL_RIGHT: case ROBTK_SCROLL_UP: val += d->acc; break; case ROBTK_SCROLL_LEFT: case ROBTK_SCROLL_DOWN: val -= d->acc; break; default: break; } if (d->touch_cb && !d->touching) { d->touch_cb (d->touch_hd, d->touch_id, true); d->touching = TRUE; } robtk_scale_update_value(d, val); return NULL; } static void create_scale_pattern(RobTkScale * d) { if (d->horiz) { d->dpat = cairo_pattern_create_linear (0.0, 0.0, 0.0, GSC_GIRTH); } else { d->dpat = cairo_pattern_create_linear (0.0, 0.0, GSC_GIRTH, 0); } cairo_pattern_add_color_stop_rgb (d->dpat, 0.0, .3, .3, .33); cairo_pattern_add_color_stop_rgb (d->dpat, 0.4, .5, .5, .55); cairo_pattern_add_color_stop_rgb (d->dpat, 1.0, .2, .2, .22); if (d->horiz) { d->fpat = cairo_pattern_create_linear (0.0, 0.0, 0.0, GSC_GIRTH); } else { d->fpat = cairo_pattern_create_linear (0.0, 0.0, GSC_GIRTH, 0); } cairo_pattern_add_color_stop_rgb (d->fpat, 0.0, .0, .0, .0); cairo_pattern_add_color_stop_rgb (d->fpat, 0.4, 1, 1, 1); cairo_pattern_add_color_stop_rgb (d->fpat, 1.0, .1, .1, .1); } #define SXX_W(minus) (d->w_width + minus - d->rw->widget_scale * ((d->bg && !d->horiz) ? d->mark_space : 0)) #define SXX_H(minus) (d->w_height + minus - d->rw->widget_scale * ((d->bg && d->horiz) ? d->mark_space : 0)) #define SXX_T(plus) (plus + d->rw->widget_scale * ((d->bg && d->horiz) ? d->mark_space : 0)) static void robtk_scale_render_metrics(RobTkScale* d) { if (d->bg) { cairo_surface_destroy(d->bg); } const float wscale = d->rw->widget_scale * RTK_SCALE_MUL; d->bg = cairo_image_surface_create (CAIRO_FORMAT_ARGB32, d->w_width * RTK_SCALE_MUL, d->w_height * RTK_SCALE_MUL); cairo_t *cr = cairo_create (d->bg); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgba (cr, .0, .0, .0, 0); cairo_rectangle (cr, 0, 0, d->w_width * RTK_SCALE_MUL, d->w_height * RTK_SCALE_MUL); cairo_fill (cr); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_rgba (cr, .7, .7, .7, 1.0); cairo_set_line_width (cr, RTK_SCALE_MUL); for (int i = 0; i < d->mark_cnt; ++i) { float v = 4.0 + robtk_scale_round_length(d, d->mark_val[i]); v *= RTK_SCALE_MUL; if (d->horiz) { if (d->mark_txt[i]) { cairo_save (cr); cairo_scale (cr, wscale, wscale); write_text_full(cr, d->mark_txt[i], d->mark_font, v / wscale, 1, -M_PI/2, 1, d->c_txt); cairo_restore (cr); } cairo_move_to(cr, v+.5 * RTK_SCALE_MUL, RTK_SCALE_MUL * SXX_T(1.5)); cairo_line_to(cr, v+.5 * RTK_SCALE_MUL, RTK_SCALE_MUL * (SXX_T(0) + SXX_H(-.5))); } else { if (d->mark_txt[i]) { cairo_save (cr); cairo_scale (cr, wscale, wscale); write_text_full(cr, d->mark_txt[i], d->mark_font, (d->w_width - 2), v / wscale, 0, 1, d->c_txt); cairo_restore (cr); } cairo_move_to(cr, 1.5 * RTK_SCALE_MUL, v+.5 * RTK_SCALE_MUL); cairo_line_to(cr, SXX_W(-.5) * RTK_SCALE_MUL , v+.5 * RTK_SCALE_MUL); } cairo_stroke(cr); } cairo_destroy(cr); } static bool robtk_scale_expose_event (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkScale * d = (RobTkScale *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); float c[4]; get_color_from_theme(1, c); cairo_set_operator (cr, CAIRO_OPERATOR_SOURCE); cairo_set_source_rgb (cr, c[0], c[1], c[2]); cairo_rectangle (cr, 0, 0, d->w_width, d->w_height); cairo_fill(cr); /* prepare tick mark surfaces */ if (d->mark_cnt > 0 && d->mark_expose) { pthread_mutex_lock (&d->_mutex); d->mark_expose = FALSE; robtk_scale_render_metrics(d); pthread_mutex_unlock (&d->_mutex); } /* tick marks */ if (d->bg) { if (!d->sensitive) { //cairo_set_operator (cr, CAIRO_OPERATOR_OVERLAY); cairo_set_operator (cr, CAIRO_OPERATOR_SOFT_LIGHT); } else { cairo_set_operator (cr, CAIRO_OPERATOR_OVER); } cairo_save (cr); cairo_scale (cr, RTK_SCALE_DIV, RTK_SCALE_DIV); cairo_set_source_surface(cr, d->bg, 0, 0); cairo_paint (cr); cairo_restore (cr); } cairo_set_operator (cr, CAIRO_OPERATOR_OVER); /* solid background */ if (d->sensitive) { cairo_matrix_t matrix; cairo_matrix_init_translate(&matrix, 0.0, -SXX_T(0)); cairo_pattern_set_matrix (d->dpat, &matrix); cairo_set_source(cr, d->dpat); } else { cairo_set_source_rgba (cr, .5, .5, .5, 1.0); } rounded_rectangle(cr, 4.5, SXX_T(4.5), SXX_W(-8), SXX_H(-8), C_RAD); cairo_fill_preserve(cr); if (d->sensitive) { cairo_set_source_rgba (cr, .0, .0, .0, 1.0); } else { cairo_set_source_rgba (cr, .5, .5, .5, 1.0); } cairo_set_line_width(cr, .75); cairo_stroke_preserve (cr); cairo_clip (cr); float val = robtk_scale_round_length(d, d->cur); /* red area, left | top */ if (d->sensitive) { cairo_set_source_rgba (cr, .5, .0, .0, .3); } else { cairo_set_source_rgba (cr, .5, .2, .2, .3); } if (d->horiz) { cairo_rectangle(cr, 3.0, SXX_T(5), val, SXX_H(-9)); } else { cairo_rectangle(cr, 5, SXX_T(3) + val, SXX_W(-9), SXX_H(-7) - val); } cairo_fill(cr); /* green area, botom | right */ if (d->sensitive) { cairo_set_source_rgba (cr, .0, .5, .0, .3); } else { cairo_set_source_rgba (cr, .2, .5, .2, .3); } if (d->horiz) { cairo_rectangle(cr, 3.0 + val, SXX_T(5), SXX_W(-7) - val, SXX_H(-9)); } else { cairo_rectangle(cr, 5, SXX_T(3), SXX_W(-9), val); } cairo_fill(cr); /* value ring */ if (d->sensitive) { cairo_matrix_t matrix; cairo_matrix_init_translate(&matrix, 0, -SXX_T(0)); cairo_pattern_set_matrix (d->fpat, &matrix); cairo_set_source(cr, d->fpat); } else { cairo_set_source_rgba (cr, .7, .7, .7, .7); } if (d->horiz) { cairo_rectangle(cr, 3.0 + val, SXX_T(5), 3, SXX_H(-9)); } else { cairo_rectangle(cr, 5, SXX_T(3) + val, SXX_W(-9), 3); } cairo_fill(cr); if (d->sensitive && (d->prelight || d->drag_x > 0)) { cairo_reset_clip (cr); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); rounded_rectangle(cr, 4.5, SXX_T(4.5), SXX_W(-8), SXX_H(-8), C_RAD); cairo_fill_preserve(cr); cairo_set_line_width(cr, .75); cairo_set_source_rgba (cr, .0, .0, .0, 1.0); cairo_stroke(cr); } return TRUE; } /****************************************************************************** * public functions */ static RobTkScale * robtk_scale_new_with_size(float min, float max, float step, int girth, int length, bool horiz) { assert(max > min); assert(step > 0); assert( (max - min) / step >= 1.0); RobTkScale *d = (RobTkScale *) malloc(sizeof(RobTkScale)); d->mark_font = get_font_from_theme(); get_color_from_theme(0, d->c_txt); pthread_mutex_init (&d->_mutex, 0); d->mark_space = 0.0; // XXX longest annotation text d->horiz = horiz; if (horiz) { d->w_width = length; d->w_height = girth; } else { d->w_width = girth; d->w_height = length; } d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "scale"); d->mark_expose = FALSE; d->cb = NULL; d->handle = NULL; d->touch_cb = NULL; d->touch_hd = NULL; d->touch_id = 0; d->touching = FALSE; d->min = min; d->max = max; d->acc = step; d->cur = min; d->dfl = min; d->sensitive = TRUE; d->prelight = FALSE; d->drag_x = d->drag_y = -1; d->bg = NULL; create_scale_pattern(d); d->mark_cnt = 0; d->mark_val = NULL; d->mark_txt = NULL; robwidget_set_size_request(d->rw, robtk_scale_size_request); robwidget_set_size_allocate(d->rw, robtk_scale_size_allocate); robwidget_set_expose_event(d->rw, robtk_scale_expose_event); robwidget_set_mouseup(d->rw, robtk_scale_mouseup); robwidget_set_mousedown(d->rw, robtk_scale_mousedown); robwidget_set_mousemove(d->rw, robtk_scale_mousemove); robwidget_set_mousescroll(d->rw, robtk_scale_scroll); robwidget_set_enter_notify(d->rw, robtk_scale_enter_notify); robwidget_set_leave_notify(d->rw, robtk_scale_leave_notify); return d; } static RobTkScale * robtk_scale_new(float min, float max, float step, bool horiz) { return robtk_scale_new_with_size(min, max, step, GSC_GIRTH, GSC_LENGTH, horiz); } static void robtk_scale_destroy(RobTkScale *d) { robwidget_destroy(d->rw); cairo_pattern_destroy(d->dpat); cairo_pattern_destroy(d->fpat); pthread_mutex_destroy(&d->_mutex); for (int i = 0; i < d->mark_cnt; ++i) { free(d->mark_txt[i]); } free(d->mark_txt); free(d->mark_val); pango_font_description_free(d->mark_font); free(d); } static RobWidget * robtk_scale_widget(RobTkScale *d) { return d->rw; } static void robtk_scale_set_callback(RobTkScale *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_scale_set_touch(RobTkScale *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) { d->touch_cb = cb; d->touch_hd = handle; d->touch_id = id; } static void robtk_scale_set_value(RobTkScale *d, float v) { v = d->min + rint((v-d->min) / d->acc ) * d->acc; robtk_scale_update_value(d, v); } static void robtk_scale_set_sensitive(RobTkScale *d, bool s) { if (d->sensitive != s) { d->sensitive = s; queue_draw(d->rw); } } static float robtk_scale_get_value(RobTkScale *d) { return (d->cur); } static void robtk_scale_set_default(RobTkScale *d, float v) { assert(v >= d->min); assert(v <= d->max); d->dfl = v; } static void robtk_scale_add_mark(RobTkScale *d, float v, const char *txt) { int tw = 0; int th = 0; if (txt && strlen(txt)) { get_text_geometry(txt, d->mark_font, &tw, &th); } pthread_mutex_lock (&d->_mutex); if ((tw + 3) > d->mark_space) { d->mark_space = tw + 3; } d->mark_val = (float *) realloc(d->mark_val, sizeof(float) * (d->mark_cnt+1)); d->mark_txt = (char **) realloc(d->mark_txt, sizeof(char*) * (d->mark_cnt+1)); d->mark_val[d->mark_cnt] = v; d->mark_txt[d->mark_cnt] = txt ? strdup(txt): NULL; d->mark_cnt++; d->mark_expose = TRUE; pthread_mutex_unlock (&d->_mutex); } #endif robtk-0.8.5/widgets/robtk_selector.h000066400000000000000000000311251463170413500175130ustar00rootroot00000000000000/* combobox-like widget - select one of N texts * * Copyright (C) 2013-2016 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_SELECTOR_H_ #define _ROB_TK_SELECTOR_H_ #include "robtk_label.h" struct select_item { RobTkLbl* lbl; float value; int width; }; typedef struct { RobWidget* rw; struct select_item *items; bool sensitive; bool prelight; int lightarr; bool wraparound; cairo_pattern_t* btn_bg; bool (*cb) (RobWidget* w, void* handle); void* handle; void (*ttip) (RobWidget* rw, bool on, void* handle); void* ttip_handle; void (*touch_cb) (void*, uint32_t, bool); void* touch_hd; uint32_t touch_id; bool touching; int active_item; int item_count; int dfl; pthread_mutex_t _mutex; float w_width, w_height; float t_width, t_height; float scale; } RobTkSelect; /****************************************************************************** * child callbacks */ static bool robtk_select_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); assert(d->items != NULL); assert(d->active_item < d->item_count); if (!d->btn_bg) { float c_bg[4]; get_color_from_theme(1, c_bg); d->btn_bg = cairo_pattern_create_linear (0.0, 0.0, 0.0, d->w_height); cairo_pattern_add_color_stop_rgb (d->btn_bg, ISBRIGHT(c_bg) ? 0.5 : 0.0, SHADE_RGB(c_bg, 1.95)); cairo_pattern_add_color_stop_rgb (d->btn_bg, ISBRIGHT(c_bg) ? 0.0 : 0.5, SHADE_RGB(c_bg, 0.75)); } cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); cairo_scale (cr, d->rw->widget_scale, d->rw->widget_scale); rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_clip(cr); /* background */ float cbg[4], cfg[4]; get_color_from_theme(0, cfg); get_color_from_theme(1, cbg); cairo_set_source_rgb (cr, cbg[0], cbg[1], cbg[2]); rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_fill(cr); /* draw arrows left + right */ int w_width = d->w_width; const int w_h2 = d->w_height / 2; cairo_set_line_width(cr, 1.0); cairo_set_source(cr, d->btn_bg); cairo_rectangle(cr, 2.5, 2.5, 14, d->w_height - 4); if (d->sensitive && d->prelight && d->lightarr == -1) { cairo_fill_preserve(cr); if (ISBRIGHT(cbg)) { cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .1); } else { cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); } } cairo_fill(cr); if (d->sensitive && (d->wraparound || d->active_item != 0)) { cairo_set_source_rgba(cr, cfg[0], cfg[1], cfg[2], 1.0); cairo_move_to(cr, 12, w_h2 - 3.5); cairo_line_to(cr, 8, w_h2 + 0.5); cairo_line_to(cr, 12, w_h2 + 4.5); cairo_stroke(cr); } cairo_set_source(cr, d->btn_bg); cairo_rectangle(cr, w_width - 15.5, 2.5, 14, d->w_height - 4); if (d->prelight && d->lightarr == 1) { cairo_fill_preserve(cr); if (ISBRIGHT(cbg)) { cairo_set_source_rgba (cr, 0.0, 0.0, 0.0, .1); } else { cairo_set_source_rgba (cr, 1.0, 1.0, 1.0, .1); } } cairo_fill(cr); if (d->sensitive && (d->wraparound || d->active_item != d->item_count -1)) { cairo_set_source_rgba(cr, cfg[0], cfg[1], cfg[2], 1.0); cairo_move_to(cr, w_width - 10.5, w_h2 - 3.5); cairo_line_to(cr, w_width - 6.5, w_h2 + 0.5); cairo_line_to(cr, w_width - 10.5, w_h2 + 4.5); cairo_stroke(cr); } cairo_save(cr); const float off = 16 + (d->w_width - 36 - d->items[d->active_item].width) / 2.0; cairo_scale (cr, 1.0 / d->rw->widget_scale, 1.0 / d->rw->widget_scale); cairo_translate(cr, floor (off * d->rw->widget_scale), floor(3. * d->rw->widget_scale)); cairo_rectangle_t a; a.x=0; a.width = ceil (d->items[d->active_item].width * d->rw->widget_scale); a.y=0; a.height = ceil (d->t_height * d->rw->widget_scale); robtk_lbl_expose_event(d->items[d->active_item].lbl->rw, cr, &a); cairo_restore(cr); cairo_set_line_width (cr, .75); rounded_rectangle(cr, 2.5, 2.5, d->w_width - 4, d->w_height -4, C_RAD); cairo_set_line_width(cr, 1.0); cairo_set_source_rgba(cr, .0, .0, .0, 1.0); cairo_stroke(cr); if (!d->sensitive) { cairo_set_source_rgba(cr, SHADE_RGB(cbg, .9) , 0.5); cairo_rectangle(cr, 0, 0, w_width, d->w_height); cairo_fill(cr); } return TRUE; } static void robtk_select_set_active_item(RobTkSelect *d, int i) { if (i<0 || i >= d->item_count) return; if (i == d->active_item) return; d->active_item = i; if (d->cb) d->cb(d->rw, d->handle); if (d->ttip) d->ttip (d->rw, false, d->ttip_handle); queue_draw(d->rw); } static RobWidget* robtk_select_mousedown(RobWidget* handle, RobTkBtnEvent *ev) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!d->prelight) { return NULL; } if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, true); } return NULL; } static RobWidget* robtk_select_mouseup(RobWidget* handle, RobTkBtnEvent *ev) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } if (!d->prelight) { if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, false); } return NULL; } if (ev->state & ROBTK_MOD_SHIFT) { robtk_select_set_active_item(d, d->dfl); return NULL; } int active_item = d->active_item; if (ev->x <= 18 * d->rw->widget_scale) { if (d->wraparound) { active_item = (d->active_item + d->item_count - 1) % d->item_count; } else { active_item--; } } else if (ev->x >= (d->w_width - 18) * d->rw->widget_scale) { if (d->wraparound) { active_item = (d->active_item + 1) % d->item_count; } else { active_item++; } } robtk_select_set_active_item(d, active_item); if (d->touch_cb) { d->touch_cb (d->touch_hd, d->touch_id, false); } return NULL; } static RobWidget* robtk_select_mousemove(RobWidget* handle, RobTkBtnEvent *ev) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } int pla = 0; if (ev->x <= 18 * d->rw->widget_scale) { if (d->wraparound || d->active_item != 0) pla = -1; } else if (ev->x >= (d->w_width - 18) * d->rw->widget_scale) { if (d->wraparound || d->active_item != d->item_count -1) pla = 1; } if (pla != d->lightarr) { if (pla == 0 && d->ttip) { d->ttip (d->rw, true, d->ttip_handle); } else if (d->ttip) { d->ttip (d->rw, false, d->ttip_handle); } d->lightarr = pla; queue_draw(d->rw); } return NULL; } static RobWidget* robtk_select_scroll(RobWidget* handle, RobTkBtnEvent *ev) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (!d->sensitive) { return NULL; } int active_item = d->active_item; switch (ev->direction) { case ROBTK_SCROLL_RIGHT: case ROBTK_SCROLL_UP: if (d->wraparound) { active_item = (d->active_item + 1) % d->item_count; } else { active_item++; } break; case ROBTK_SCROLL_LEFT: case ROBTK_SCROLL_DOWN: if (d->wraparound) { active_item = (d->active_item + d->item_count - 1) % d->item_count; } else { active_item--; } break; default: break; } if (d->touch_cb && !d->touching) { d->touch_cb (d->touch_hd, d->touch_id, true); d->touching = TRUE; } robtk_select_set_active_item(d, active_item); return handle; } static void robtk_select_enter_notify(RobWidget *handle) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (!d->prelight) { d->prelight = TRUE; queue_draw(d->rw); } if (d->ttip) { d->ttip (d->rw, true, d->ttip_handle); } } static void robtk_select_leave_notify(RobWidget *handle) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (d->touch_cb && d->touching) { d->touch_cb (d->touch_hd, d->touch_id, false); d->touching = FALSE; } if (d->prelight) { d->prelight = FALSE; queue_draw(d->rw); } if (d->ttip) { d->ttip (d->rw, false, d->ttip_handle); } } static void robtk_select_size_request(RobWidget* handle, int *w, int *h) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); if (d->scale != d->rw->widget_scale) { d->scale = d->rw->widget_scale; for (int i = 0; i < d->item_count; ++i) { d->items[i].lbl->rw->widget_scale = d->scale; } } // currently assumes text widgets are instantiated with scale 1.0 *w = d->rw->widget_scale * (d->t_width + 36); *h = d->rw->widget_scale * MAX(16, 6 + d->t_height); } static void robtk_select_size_allocate(RobWidget* handle, int w, int h) { RobTkSelect * d = (RobTkSelect *)GET_HANDLE(handle); d->w_width = w / d->rw->widget_scale; d->w_height = MAX(16, 6 + d->t_height); robwidget_set_size(handle, w, h); } /****************************************************************************** * public functions */ static RobTkSelect * robtk_select_new() { RobTkSelect *d = (RobTkSelect *) malloc(sizeof(RobTkSelect)); d->sensitive = TRUE; d->prelight = FALSE; d->lightarr = 0; d->cb = NULL; d->handle = NULL; d->ttip = NULL; d->ttip_handle = NULL; d->touch_cb = NULL; d->touch_hd = NULL; d->touch_id = 0; d->touching = FALSE; d->scale = 1.0; pthread_mutex_init (&d->_mutex, 0); d->wraparound = FALSE; d->items = NULL; d->btn_bg = NULL; d->item_count = d->active_item = d->dfl = 0; d->w_width = d->w_height = 0; d->t_width = d->t_height = 0; d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "select"); robwidget_set_expose_event(d->rw, robtk_select_expose_event); robwidget_set_mouseup(d->rw, robtk_select_mouseup); robwidget_set_mousedown(d->rw, robtk_select_mousedown); robwidget_set_mousemove(d->rw, robtk_select_mousemove); robwidget_set_mousescroll(d->rw, robtk_select_scroll); robwidget_set_enter_notify(d->rw, robtk_select_enter_notify); robwidget_set_leave_notify(d->rw, robtk_select_leave_notify); return d; } static void robtk_select_destroy(RobTkSelect *d) { int i; for (i=0; i < d->item_count ; ++i) { robtk_lbl_destroy(d->items[i].lbl); } robwidget_destroy(d->rw); if (d->btn_bg) cairo_pattern_destroy(d->btn_bg); free(d->items); pthread_mutex_destroy(&d->_mutex); free(d); } static void robtk_select_set_alignment(RobTkSelect *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static void robtk_select_add_item(RobTkSelect *d, float val, const char *txt) { d->items = (struct select_item *) realloc(d->items, sizeof(struct select_item) * (d->item_count + 1)); d->items[d->item_count].value = val; d->items[d->item_count].lbl = robtk_lbl_new(txt); int w, h; priv_lbl_size_request(d->items[d->item_count].lbl->rw, &w, &h); assert (d->rw->widget_scale == 1.0); // XXX d->t_width = MAX(d->t_width, w); d->t_height = MAX(d->t_height, h); d->items[d->item_count].width = w; d->item_count++; robwidget_set_size_request(d->rw, robtk_select_size_request); robwidget_set_size_allocate(d->rw, robtk_select_size_allocate); } static RobWidget * robtk_select_widget(RobTkSelect *d) { return d->rw; } static void robtk_select_set_callback(RobTkSelect *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_select_annotation_callback(RobTkSelect *d, void (*cb) (RobWidget* w, bool, void* handle), void* handle) { d->ttip = cb; d->ttip_handle = handle; } static void robtk_select_set_touch(RobTkSelect *d, void (*cb) (void*, uint32_t, bool), void* handle, uint32_t id) { d->touch_cb = cb; d->touch_hd = handle; d->touch_id = id; } static void robtk_select_set_default_item(RobTkSelect *d, int i) { d->dfl = i; } static void robtk_select_set_item(RobTkSelect *d, int i) { robtk_select_set_active_item(d, i); } static void robtk_select_set_value(RobTkSelect *d, float v) { assert(d->item_count > 0); int i; int s = 0; float diff = fabsf(v - d->items[0].value); for (i=1; i < d->item_count; ++i) { float df = fabsf(v - d->items[i].value); if (df < diff) { s = i; diff = df; } } robtk_select_set_active_item(d, s); } static void robtk_select_set_sensitive(RobTkSelect *d, bool s) { if (d->sensitive != s) { d->sensitive = s; } queue_draw(d->rw); } static int robtk_select_get_item(RobTkSelect *d) { return d->active_item; } static float robtk_select_get_value(RobTkSelect *d) { return d->items[d->active_item].value; } static void robtk_select_set_wrap(RobTkSelect *d, bool en) { d->wraparound = en; } static bool robtk_select_get_wrap(RobTkSelect *d) { return d->wraparound; } #endif robtk-0.8.5/widgets/robtk_separator.h000066400000000000000000000071001463170413500176670ustar00rootroot00000000000000/* separator line widget * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_SEP_H_ #define _ROB_TK_SEP_H_ typedef struct { RobWidget *rw; bool horiz; float m_width, m_height; float w_width, w_height; float line_width; double dash; double dashoffset; } RobTkSep; static bool robtk_sep_expose_event(RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { RobTkSep* d = (RobTkSep *)GET_HANDLE(handle); cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); float c[4]; get_color_from_theme(1, c); cairo_set_source_rgb (cr, c[0], c[1], c[2]); cairo_rectangle (cr, 0, 0, d->w_width, d->w_height); cairo_fill(cr); get_color_from_theme(0, c); cairo_set_source_rgba (cr, c[0], c[1], c[2], .7); if (d->line_width <= 0) return TRUE; if (d->dash > 0) { cairo_set_dash (cr, &d->dash, 1, d->dashoffset); } cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_line_cap(cr, CAIRO_LINE_CAP_BUTT); cairo_set_line_width(cr, 1.0); if (d->horiz) { cairo_move_to (cr, 0.5, rint(d->w_height * .5) - .5); cairo_line_to (cr, d->w_width - 0.5 , rint(d->w_height *.5) - .5); } else { cairo_move_to (cr, rint(d->w_width * .5) - .5, 0.5); cairo_line_to (cr, rint(d->w_width * .5) - .5, d->w_height - .5); } cairo_stroke(cr); return TRUE; } /****************************************************************************** * RobWidget stuff */ static void priv_sep_size_request(RobWidget* handle, int *w, int *h) { RobTkSep* d = (RobTkSep*)GET_HANDLE(handle); *w = d->m_width; *h = d->m_height; } static void priv_sep_size_allocate(RobWidget* handle, int w, int h) { RobTkSep* d = (RobTkSep*)GET_HANDLE(handle); d->w_width = w; d->w_height = h; robwidget_set_size(handle, d->w_width, d->w_height); } /****************************************************************************** * public functions */ static RobTkSep * robtk_sep_new(bool horizontal) { RobTkSep *d = (RobTkSep *) malloc(sizeof(RobTkSep)); d->horiz = horizontal; d->w_width = 4; d->w_height = 4; d->m_width = 4; d->m_height = 4; d->line_width = 1.0; d->dash = 0; d->dashoffset = 0; d->rw = robwidget_new(d); if (horizontal) { ROBWIDGET_SETNAME(d->rw, "hsep"); } else { ROBWIDGET_SETNAME(d->rw, "vsep"); } robwidget_set_expose_event(d->rw, robtk_sep_expose_event); robwidget_set_size_request(d->rw, priv_sep_size_request); robwidget_set_size_allocate(d->rw, priv_sep_size_allocate); return d; } static void robtk_sep_destroy(RobTkSep *d) { robwidget_destroy(d->rw); free(d); } static void robtk_sep_set_alignment(RobTkSep *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static void robtk_sep_set_dash(RobTkSep *d, double dash, double offset) { d->dash = dash; d->dashoffset = offset; } static void robtk_sep_set_linewidth(RobTkSep *d, float lw) { d->line_width = lw; } static RobWidget * robtk_sep_widget(RobTkSep *d) { return d->rw; } #endif robtk-0.8.5/widgets/robtk_spinner.h000066400000000000000000000126731463170413500173600ustar00rootroot00000000000000/* dial with numeric display / spinbox * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_SPIN_H_ #define _ROB_TK_SPIN_H_ #include "robtk_dial.h" #include "robtk_label.h" #define GSP_WIDTH 25 #define GSP_HEIGHT 30 #define GSP_RADIUS 10 #define GSP_CX 12.5 #define GSP_CY 12.5 typedef struct { RobTkDial *dial; RobWidget* rw; RobTkLbl* lbl_r; RobTkLbl* lbl_l; bool sensitive; char prec_fmt[8]; bool (*cb) (RobWidget* w, void* handle); void* handle; int lbl; pthread_mutex_t _mutex; } RobTkSpin; static bool robtk_spin_render(RobTkSpin *d){ pthread_mutex_lock (&d->_mutex); char buf[32]; #ifdef _WIN32 sprintf(buf, d->prec_fmt, robtk_dial_get_value(d->dial)); #else snprintf(buf, 32, d->prec_fmt, robtk_dial_get_value(d->dial)); #endif buf[31] = '\0'; if (d->lbl & 1) robtk_lbl_set_text(d->lbl_l, buf); if (d->lbl & 2) robtk_lbl_set_text(d->lbl_r, buf); pthread_mutex_unlock (&d->_mutex); return TRUE; } /****************************************************************************** * child callbacks */ static bool robtk_spin_callback(RobWidget *w, void* handle) { RobTkSpin *d = (RobTkSpin *) handle; robtk_spin_render(d); if (d->cb) d->cb(robtk_dial_widget(d->dial), d->handle); return TRUE; } static void robtk_spin_position_set (RobWidget *rw, const int pw, const int ph) { //override default extra-space table packing rw->area.x = rint((pw - rw->area.width) * rw->xalign); rw->area.y = rint((ph - rw->area.height) * rw->yalign); } /****************************************************************************** * public functions */ static void robtk_spin_set_digits(RobTkSpin *d, int prec) { if (prec > 4) prec = 4; if (prec <= 0) { sprintf(d->prec_fmt,"%%.0f"); } else { sprintf(d->prec_fmt,"%%.%df", prec); } robtk_spin_render(d); } static RobTkSpin * robtk_spin_new(float min, float max, float step) { RobTkSpin *d = (RobTkSpin *) malloc(sizeof(RobTkSpin)); d->sensitive = TRUE; d->cb = NULL; d->handle = NULL; d->lbl = 2; pthread_mutex_init (&d->_mutex, 0); d->dial = robtk_dial_new_with_size(min, max, step, GSP_WIDTH, GSP_HEIGHT, GSP_CX, GSP_CY, GSP_RADIUS); robtk_dial_set_callback(d->dial, robtk_spin_callback, d); d->lbl_r = robtk_lbl_new(""); d->lbl_l = robtk_lbl_new(""); d->rw = rob_hbox_new(FALSE, 2); rob_hbox_child_pack(d->rw, robtk_lbl_widget(d->lbl_l), FALSE, FALSE); rob_hbox_child_pack(d->rw, robtk_dial_widget(d->dial), FALSE, FALSE); rob_hbox_child_pack(d->rw, robtk_lbl_widget(d->lbl_r), FALSE, FALSE); d->rw->position_set = robtk_spin_position_set; robtk_spin_set_digits(d, - floorf(log10f(step))); robtk_spin_callback(0, d); // set value return d; } static void robtk_spin_destroy(RobTkSpin *d) { robtk_dial_destroy(d->dial); robtk_lbl_destroy(d->lbl_r); robtk_lbl_destroy(d->lbl_l); rob_box_destroy(d->rw); pthread_mutex_destroy(&d->_mutex); free(d); } static void robtk_spin_set_alignment(RobTkSpin *d, float x, float y) { #ifndef GTK_BACKEND robwidget_set_alignment(d->rw, x, y); #else if (x > .5) { gtk_box_set_child_packing(GTK_BOX(d->rw->c), d->lbl_l->rw->c, TRUE, FALSE, 0, GTK_PACK_START); } else { gtk_box_set_child_packing(GTK_BOX(d->rw->c), d->lbl_l->rw->c, FALSE, FALSE, 0, GTK_PACK_START); } #endif } static void robtk_spin_label_width(RobTkSpin *d, float left, float right) { if (left < 0) { robwidget_hide(robtk_lbl_widget(d->lbl_l), false); } else { robtk_lbl_set_min_geometry(d->lbl_l, (float) left, 0); robwidget_show(robtk_lbl_widget(d->lbl_l), false); } if (right < 0) { robwidget_hide(robtk_lbl_widget(d->lbl_r), false); } else { robtk_lbl_set_min_geometry(d->lbl_r, (float) right, 0); robwidget_show(robtk_lbl_widget(d->lbl_r), false); } robtk_spin_render(d); } static void robtk_spin_set_label_pos(RobTkSpin *d, int p) { d->lbl = p&3; if (!(d->lbl & 1)) robtk_lbl_set_text(d->lbl_l, ""); if (!(d->lbl & 2)) robtk_lbl_set_text(d->lbl_r, ""); robtk_spin_render(d); } static RobWidget * robtk_spin_widget(RobTkSpin *d) { return d->rw; } static void robtk_spin_set_callback(RobTkSpin *d, bool (*cb) (RobWidget* w, void* handle), void* handle) { d->cb = cb; d->handle = handle; } static void robtk_spin_set_default(RobTkSpin *d, float v) { robtk_dial_set_default(d->dial, v); } static void robtk_spin_set_value(RobTkSpin *d, float v) { robtk_dial_set_value(d->dial, v); } static void robtk_spin_set_sensitive(RobTkSpin *d, bool s) { if (d->sensitive != s) { d->sensitive = s; robtk_lbl_set_sensitive(d->lbl_r, s); robtk_lbl_set_sensitive(d->lbl_l, s); } robtk_dial_set_sensitive(d->dial, s); } static float robtk_spin_get_value(RobTkSpin *d) { return robtk_dial_get_value(d->dial); } static bool robtk_spin_update_range (RobTkSpin *d, float min, float max, float step) { return robtk_dial_update_range(d->dial, min, max, step); } #endif robtk-0.8.5/widgets/robtk_xyplot.h000066400000000000000000000226761463170413500172450ustar00rootroot00000000000000/* XY plot/drawing area * * Copyright (C) 2013 Robin Gareus * * This program is free software; you can redistribute it and/or modify * it under the terms of the GNU General Public License as published by * the Free Software Foundation; either version 2, or (at your option) * any later version. * * This program is distributed in the hope that it will be useful, * but WITHOUT ANY WARRANTY; without even the implied warranty of * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the * GNU General Public License for more details. * * You should have received a copy of the GNU General Public License * along with this program; if not, write to the Free Software Foundation, * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA. */ #ifndef _ROB_TK_XYP_H_ #define _ROB_TK_XYP_H_ typedef struct { RobWidget *rw; float w_width, w_height; cairo_surface_t* bg; float bg_scale; void (*clip_cb) (cairo_t *cr, void* handle); void* handle; float line_width; float col[4]; pthread_mutex_t _mutex; uint32_t n_points; uint32_t n_alloc; float *points_x; float *points_y; float map_x_scale; float map_x_offset; float map_y_scale; float map_y_offset; float map_x0; float map_xw; float map_y0; float map_yh; } RobTkXYp; enum RobTkXYmode { RobTkXY_yraw_line, RobTkXY_yraw_zline, RobTkXY_yraw_point, RobTkXY_yavg_line, RobTkXY_yavg_zline, RobTkXY_yavg_point, RobTkXY_ymax_line, RobTkXY_ymax_zline, RobTkXY_ymax_point, }; static inline void robtk_xydraw_expose_common(RobTkXYp* d, cairo_t* cr, cairo_rectangle_t* ev) { cairo_rectangle (cr, ev->x, ev->y, ev->width, ev->height); cairo_clip (cr); if (d->bg) { cairo_save (cr); cairo_scale (cr, d->bg_scale, d->bg_scale); cairo_set_operator (cr, CAIRO_OPERATOR_OVER); cairo_set_source_surface(cr, d->bg, 0, 0); cairo_paint (cr); cairo_restore (cr); } else { cairo_rectangle (cr, 0, 0, d->w_width, d->w_height); cairo_set_source_rgba(cr, 0, 0, 0, 1); cairo_fill(cr); } if (d->clip_cb) d->clip_cb(cr, d->handle); } #define GEN_DRAW_FN(ID, PROC, DRAW) \ static bool robtk_xydraw_expose_ ## ID (RobWidget* handle, cairo_t* cr, cairo_rectangle_t* ev) { \ RobTkXYp* d = (RobTkXYp *)GET_HANDLE(handle); \ robtk_xydraw_expose_common(d, cr, ev); \ if (pthread_mutex_trylock(&d->_mutex)) return FALSE; \ /* localize variables */ \ const float x0 = d->map_x0; \ const float y0 = d->map_y0; \ const float fx = d->map_xw * d->map_x_scale; \ const float fy = -(d->map_yh * d->map_y_scale); \ const float x1 = d->map_x0 + d->map_xw; \ const float y1 = d->map_y0 + d->map_yh; \ const float ox = d->map_x0 + d->map_x_offset * d->map_xw; \ const float oy = y1 - d->map_y_offset * d->map_yh; \ PROC ## _SETUP \ DRAW ## _SETUP \ for (uint32_t i = 0; i < d->n_points; ++i) { \ float x = d->points_x[i] * fx + ox; \ float y = d->points_y[i] * fy + oy; \ float cx, cy; \ if (x < x0) continue; \ if (y < y0) y = y0; \ if (x > x1) continue; \ if (y > y1) y = y1; \ PROC ## _PROC \ DRAW ## _DRAW \ } \ if (PROC ## _POST) { \ for (uint32_t i = d->n_points; i < d->n_points + 1; ++i) { \ float x = -1; \ float y = -1; \ float cx, cy; \ PROC ## _PROC \ DRAW ## _DRAW \ } \ } \ pthread_mutex_unlock (&d->_mutex); \ DRAW ## _FINISH \ return TRUE; \ } /**** drawing routines ****/ /* line */ #define DR_LINE_SETUP {} #define DR_LINE_DRAW \ if (i==0) cairo_move_to(cr, cx, cy+.5);\ else cairo_line_to(cr, cx, cy+.5); #define DR_LINE_FINISH \ if (d->n_points > 0) { \ cairo_set_line_width (cr, d->line_width); \ cairo_set_source_rgba(cr, d->col[0], d->col[1], d->col[2], d->col[3]); \ cairo_stroke(cr); \ } /* line from bottom to ypos */ #define DR_ZLINE_SETUP \ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);\ cairo_set_line_width (cr, d->line_width);\ cairo_set_source_rgba(cr, d->col[0], d->col[1], d->col[2], d->col[3]); #define DR_ZLINE_DRAW \ cairo_move_to(cr, cx, cy+.5); \ cairo_line_to(cr, cx, y1); \ cairo_stroke(cr); #define DR_ZLINE_FINISH {} /* points */ #define DR_POINT_SETUP \ cairo_set_line_cap(cr, CAIRO_LINE_CAP_ROUND);\ cairo_set_line_width (cr, d->line_width);\ cairo_set_source_rgba(cr, d->col[0], d->col[1], d->col[2], d->col[3]); #define DR_POINT_DRAW \ cairo_move_to(cr, cx, cy+.5);\ cairo_close_path(cr);\ cairo_stroke(cr); #define DR_POINT_FINISH {} /**** data pre-processing routines ****/ /* noop */ #define PR_RAW_SETUP {} #define PR_RAW_PROC \ cx = MAX(0, x - .5); \ cy = y; #define PR_RAW_POST 0 /* avg y-value for each x-pos */ #define PR_YAVG_SETUP \ int px = -1; \ float yavg = 0; \ int ycnt = 0; #define PR_YAVG_PROC \ if (px == rint(x)) { \ yavg += y; \ ycnt ++; \ continue; \ } \ if (ycnt > 0) { \ cy = (yavg) / (float)(ycnt); \ } else { \ continue; \ } \ yavg = y; ycnt = 1; \ cx = MAX(0, px - .5); \ px = rintf(x); #define PR_YAVG_POST 1 /* max y-value for each x-pos */ #define PR_YMAX_SETUP \ int px = d->n_points > 0 ? d->points_x[0] * fx + ox : -1; \ float ypeak = y1; \ #define PR_YMAX_PROC \ if (px == rint(x)) { \ if (ypeak > y) ypeak = y; \ continue; \ } \ cy = ypeak; \ ypeak = y; \ cx = MAX(0, px - .5); \ px = rintf(x); #define PR_YMAX_POST 1 GEN_DRAW_FN(yraw_line, PR_RAW, DR_LINE) GEN_DRAW_FN(yraw_zline, PR_RAW, DR_ZLINE) GEN_DRAW_FN(yraw_point, PR_RAW, DR_POINT) GEN_DRAW_FN(yavg_line, PR_YAVG, DR_LINE) GEN_DRAW_FN(yavg_zline, PR_YAVG, DR_ZLINE) GEN_DRAW_FN(yavg_point, PR_YAVG, DR_POINT) GEN_DRAW_FN(ymax_line, PR_YMAX, DR_LINE) GEN_DRAW_FN(ymax_zline, PR_YMAX, DR_ZLINE) GEN_DRAW_FN(ymax_point, PR_YMAX, DR_POINT) /****************************************************************************** * RobWidget stuff */ static void priv_xydraw_size_request(RobWidget* handle, int *w, int *h) { RobTkXYp* d = (RobTkXYp*)GET_HANDLE(handle); *w = d->w_width; *h = d->w_height; } /****************************************************************************** * public functions */ static RobTkXYp * robtk_xydraw_new(int w, int h) { RobTkXYp *d = (RobTkXYp *) malloc(sizeof(RobTkXYp)); d->w_width = w; d->w_height = h; d->line_width = 1.5; d->clip_cb = NULL; d->handle = NULL; d->bg = NULL; d->bg_scale = 1.0; d->n_points = 0; d->n_alloc = 0; d->points_x = NULL; d->points_y = NULL; d->map_x_scale = 1.0; d->map_x_offset = 0.0; d->map_y_scale = 1.0; d->map_y_offset = 0.0; d->map_x0 = 0; d->map_y0 = 0; d->map_xw = w; d->map_yh = h; d->col[0] = .9; d->col[1] = .3; d->col[2] = .2; d->col[3] = 1.0; pthread_mutex_init (&d->_mutex, 0); d->rw = robwidget_new(d); ROBWIDGET_SETNAME(d->rw, "xydraw"); robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yraw_line); robwidget_set_size_request(d->rw, priv_xydraw_size_request); return d; } static void robtk_xydraw_destroy(RobTkXYp *d) { pthread_mutex_destroy(&d->_mutex); robwidget_destroy(d->rw); d->n_points = 0; d->n_alloc = 0; free(d->points_x); free(d->points_y); free(d); } static void robtk_xydraw_set_alignment(RobTkXYp *d, float x, float y) { robwidget_set_alignment(d->rw, x, y); } static void robtk_xydraw_set_linewidth(RobTkXYp *d, float lw) { d->line_width = lw; } static void robtk_xydraw_set_drawing_mode(RobTkXYp *d, int mode) { switch(mode) { default: case RobTkXY_yraw_line: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yraw_line); break; case RobTkXY_yraw_zline: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yraw_zline); break; case RobTkXY_yraw_point: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yraw_point); break; case RobTkXY_yavg_line: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yavg_line); break; case RobTkXY_yavg_zline: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yavg_zline); break; case RobTkXY_yavg_point: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_yavg_point); break; case RobTkXY_ymax_line: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_ymax_line); break; case RobTkXY_ymax_zline: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_ymax_zline); break; case RobTkXY_ymax_point: robwidget_set_expose_event(d->rw, robtk_xydraw_expose_ymax_point); break; } } static void robtk_xydraw_set_mapping(RobTkXYp *d, float xs, float xo, float ys, float yo) { d->map_x_scale = xs; d->map_x_offset = xo; d->map_y_scale = ys; d->map_y_offset = yo; } static void robtk_xydraw_set_area(RobTkXYp *d, float x0, float y0, float w, float h) { d->map_x0 = x0; d->map_y0 = y0; d->map_xw = w; d->map_yh = h; } static void robtk_xydraw_set_clip_callback(RobTkXYp *d, void (*cb) (cairo_t* cr, void* handle), void* handle) { d->clip_cb = cb; d->handle = handle; } static void robtk_xydraw_set_color(RobTkXYp *d, float r, float g, float b, float a) { d->col[0] = r; d->col[1] = g; d->col[2] = b; d->col[3] = a; } static void robtk_xydraw_set_points(RobTkXYp *d, const uint32_t np, const float *xp, const float *yp) { pthread_mutex_lock (&d->_mutex); if (np > d->n_alloc) { d->points_x = (float*) realloc(d->points_x, sizeof(float) * np); d->points_y = (float*) realloc(d->points_y, sizeof(float) * np); d->n_alloc = np; } memcpy(d->points_x, xp, sizeof(float) * np); memcpy(d->points_y, yp, sizeof(float) * np); d->n_points = np; pthread_mutex_unlock (&d->_mutex); queue_draw(d->rw); } static void robtk_xydraw_set_scaled_surface(RobTkXYp *d, cairo_surface_t *s, const float sc) { d->bg = s; d->bg_scale = 1.0 / sc; } static void robtk_xydraw_set_surface(RobTkXYp *d, cairo_surface_t *s) { d->bg = s; d->bg_scale = 1.0; } static RobWidget * robtk_xydraw_widget(RobTkXYp *d) { return d->rw; } #endif robtk-0.8.5/win_icon.rc000066400000000000000000000000661463170413500150060ustar00rootroot00000000000000IDI_ICON1 ICON DISCARDABLE "img/x42.ico"