1 /*
2  * This program is free software; you can redistribute it and/or
3  * modify it under the terms of the GNU General Public License
4  * as published by the Free Software Foundation; either version 2
5  * of the License, or (at your option) any later version.
6  *
7  * This program is distributed in the hope that it will be useful,
8  * but WITHOUT ANY WARRANTY; without even the implied warranty of
9  * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE.  See the
10  * GNU General Public License for more details.
11  *
12  * You should have received a copy of the GNU General Public License
13  * along with this program; if not, write to the Free Software Foundation,
14  * Inc., 51 Franklin Street, Fifth Floor, Boston, MA 02110-1301, USA.
15  *
16  * The Original Code is Copyright (C) 2010 Blender Foundation.
17  * All rights reserved.
18  */
19 
20 /** \file
21  * \ingroup wm
22  *
23  * Our own drag-and-drop, drag state and drop boxes.
24  */
25 
26 #include <string.h>
27 
28 #include "DNA_screen_types.h"
29 #include "DNA_windowmanager_types.h"
30 
31 #include "MEM_guardedalloc.h"
32 
33 #include "BLT_translation.h"
34 
35 #include "BLI_blenlib.h"
36 
37 #include "BIF_glutil.h"
38 
39 #include "BKE_context.h"
40 #include "BKE_idtype.h"
41 
42 #include "GPU_shader.h"
43 #include "GPU_state.h"
44 #include "GPU_viewport.h"
45 
46 #include "IMB_imbuf_types.h"
47 
48 #include "UI_interface.h"
49 #include "UI_interface_icons.h"
50 
51 #include "RNA_access.h"
52 
53 #include "WM_api.h"
54 #include "WM_types.h"
55 #include "wm_event_system.h"
56 
57 /* ****************************************************** */
58 
59 static ListBase dropboxes = {NULL, NULL};
60 
61 /* drop box maps are stored global for now */
62 /* these are part of blender's UI/space specs, and not like keymaps */
63 /* when editors become configurable, they can add own dropbox definitions */
64 
65 typedef struct wmDropBoxMap {
66   struct wmDropBoxMap *next, *prev;
67 
68   ListBase dropboxes;
69   short spaceid, regionid;
70   char idname[KMAP_MAX_NAME];
71 
72 } wmDropBoxMap;
73 
74 /* spaceid/regionid is zero for window drop maps */
WM_dropboxmap_find(const char * idname,int spaceid,int regionid)75 ListBase *WM_dropboxmap_find(const char *idname, int spaceid, int regionid)
76 {
77   LISTBASE_FOREACH (wmDropBoxMap *, dm, &dropboxes) {
78     if (dm->spaceid == spaceid && dm->regionid == regionid) {
79       if (STREQLEN(idname, dm->idname, KMAP_MAX_NAME)) {
80         return &dm->dropboxes;
81       }
82     }
83   }
84 
85   wmDropBoxMap *dm = MEM_callocN(sizeof(struct wmDropBoxMap), "dropmap list");
86   BLI_strncpy(dm->idname, idname, KMAP_MAX_NAME);
87   dm->spaceid = spaceid;
88   dm->regionid = regionid;
89   BLI_addtail(&dropboxes, dm);
90 
91   return &dm->dropboxes;
92 }
93 
WM_dropbox_add(ListBase * lb,const char * idname,bool (* poll)(bContext *,wmDrag *,const wmEvent *,const char **),void (* copy)(wmDrag *,wmDropBox *))94 wmDropBox *WM_dropbox_add(ListBase *lb,
95                           const char *idname,
96                           bool (*poll)(bContext *, wmDrag *, const wmEvent *, const char **),
97                           void (*copy)(wmDrag *, wmDropBox *))
98 {
99   wmDropBox *drop = MEM_callocN(sizeof(wmDropBox), "wmDropBox");
100   drop->poll = poll;
101   drop->copy = copy;
102   drop->ot = WM_operatortype_find(idname, 0);
103   drop->opcontext = WM_OP_INVOKE_DEFAULT;
104 
105   if (drop->ot == NULL) {
106     MEM_freeN(drop);
107     printf("Error: dropbox with unknown operator: %s\n", idname);
108     return NULL;
109   }
110   WM_operator_properties_alloc(&(drop->ptr), &(drop->properties), idname);
111 
112   BLI_addtail(lb, drop);
113 
114   return drop;
115 }
116 
wm_dropbox_free(void)117 void wm_dropbox_free(void)
118 {
119 
120   LISTBASE_FOREACH (wmDropBoxMap *, dm, &dropboxes) {
121     LISTBASE_FOREACH (wmDropBox *, drop, &dm->dropboxes) {
122       if (drop->ptr) {
123         WM_operator_properties_free(drop->ptr);
124         MEM_freeN(drop->ptr);
125       }
126     }
127     BLI_freelistN(&dm->dropboxes);
128   }
129 
130   BLI_freelistN(&dropboxes);
131 }
132 
133 /* *********************************** */
134 
135 /* note that the pointer should be valid allocated and not on stack */
WM_event_start_drag(struct bContext * C,int icon,int type,void * poin,double value,unsigned int flags)136 wmDrag *WM_event_start_drag(
137     struct bContext *C, int icon, int type, void *poin, double value, unsigned int flags)
138 {
139   wmWindowManager *wm = CTX_wm_manager(C);
140   wmDrag *drag = MEM_callocN(sizeof(struct wmDrag), "new drag");
141 
142   /* keep track of future multitouch drag too, add a mousepointer id or so */
143   /* if multiple drags are added, they're drawn as list */
144 
145   BLI_addtail(&wm->drags, drag);
146   drag->flags = flags;
147   drag->icon = icon;
148   drag->type = type;
149   if (type == WM_DRAG_PATH) {
150     BLI_strncpy(drag->path, poin, FILE_MAX);
151   }
152   else if (type == WM_DRAG_ID) {
153     if (poin) {
154       WM_drag_add_ID(drag, poin, NULL);
155     }
156   }
157   else {
158     drag->poin = poin;
159   }
160   drag->value = value;
161 
162   return drag;
163 }
164 
WM_event_drag_image(wmDrag * drag,ImBuf * imb,float scale,int sx,int sy)165 void WM_event_drag_image(wmDrag *drag, ImBuf *imb, float scale, int sx, int sy)
166 {
167   drag->imb = imb;
168   drag->scale = scale;
169   drag->sx = sx;
170   drag->sy = sy;
171 }
172 
WM_drag_free(wmDrag * drag)173 void WM_drag_free(wmDrag *drag)
174 {
175   if ((drag->flags & WM_DRAG_FREE_DATA) && drag->poin) {
176     MEM_freeN(drag->poin);
177   }
178 
179   BLI_freelistN(&drag->ids);
180   MEM_freeN(drag);
181 }
182 
WM_drag_free_list(struct ListBase * lb)183 void WM_drag_free_list(struct ListBase *lb)
184 {
185   wmDrag *drag;
186   while ((drag = BLI_pophead(lb))) {
187     WM_drag_free(drag);
188   }
189 }
190 
dropbox_active(bContext * C,ListBase * handlers,wmDrag * drag,const wmEvent * event)191 static const char *dropbox_active(bContext *C,
192                                   ListBase *handlers,
193                                   wmDrag *drag,
194                                   const wmEvent *event)
195 {
196   LISTBASE_FOREACH (wmEventHandler *, handler_base, handlers) {
197     if (handler_base->type == WM_HANDLER_TYPE_DROPBOX) {
198       wmEventHandler_Dropbox *handler = (wmEventHandler_Dropbox *)handler_base;
199       if (handler->dropboxes) {
200         LISTBASE_FOREACH (wmDropBox *, drop, handler->dropboxes) {
201           const char *tooltip = NULL;
202           if (drop->poll(C, drag, event, &tooltip)) {
203             /* XXX Doing translation here might not be ideal, but later we have no more
204              *     access to ot (and hence op context)... */
205             return (tooltip) ? tooltip : WM_operatortype_name(drop->ot, drop->ptr);
206           }
207         }
208       }
209     }
210   }
211   return NULL;
212 }
213 
214 /* return active operator name when mouse is in box */
wm_dropbox_active(bContext * C,wmDrag * drag,const wmEvent * event)215 static const char *wm_dropbox_active(bContext *C, wmDrag *drag, const wmEvent *event)
216 {
217   wmWindow *win = CTX_wm_window(C);
218   ScrArea *area = CTX_wm_area(C);
219   ARegion *region = CTX_wm_region(C);
220   const char *name;
221 
222   name = dropbox_active(C, &win->handlers, drag, event);
223   if (name) {
224     return name;
225   }
226 
227   name = dropbox_active(C, &area->handlers, drag, event);
228   if (name) {
229     return name;
230   }
231 
232   name = dropbox_active(C, &region->handlers, drag, event);
233   if (name) {
234     return name;
235   }
236 
237   return NULL;
238 }
239 
wm_drop_operator_options(bContext * C,wmDrag * drag,const wmEvent * event)240 static void wm_drop_operator_options(bContext *C, wmDrag *drag, const wmEvent *event)
241 {
242   wmWindow *win = CTX_wm_window(C);
243   const int winsize_x = WM_window_pixels_x(win);
244   const int winsize_y = WM_window_pixels_y(win);
245 
246   /* for multiwin drags, we only do this if mouse inside */
247   if (event->x < 0 || event->y < 0 || event->x > winsize_x || event->y > winsize_y) {
248     return;
249   }
250 
251   drag->opname[0] = 0;
252 
253   /* check buttons (XXX todo rna and value) */
254   if (UI_but_active_drop_name(C)) {
255     BLI_strncpy(drag->opname, IFACE_("Paste name"), sizeof(drag->opname));
256   }
257   else {
258     const char *opname = wm_dropbox_active(C, drag, event);
259 
260     if (opname) {
261       BLI_strncpy(drag->opname, opname, sizeof(drag->opname));
262       // WM_cursor_modal_set(win, WM_CURSOR_COPY);
263     }
264     // else
265     //  WM_cursor_modal_restore(win);
266     /* unsure about cursor type, feels to be too much */
267   }
268 }
269 
270 /* called in inner handler loop, region context */
wm_drags_check_ops(bContext * C,const wmEvent * event)271 void wm_drags_check_ops(bContext *C, const wmEvent *event)
272 {
273   wmWindowManager *wm = CTX_wm_manager(C);
274 
275   LISTBASE_FOREACH (wmDrag *, drag, &wm->drags) {
276     wm_drop_operator_options(C, drag, event);
277   }
278 }
279 
280 /* ************** IDs ***************** */
281 
WM_drag_add_ID(wmDrag * drag,ID * id,ID * from_parent)282 void WM_drag_add_ID(wmDrag *drag, ID *id, ID *from_parent)
283 {
284   /* Don't drag the same ID twice. */
285   LISTBASE_FOREACH (wmDragID *, drag_id, &drag->ids) {
286     if (drag_id->id == id) {
287       if (drag_id->from_parent == NULL) {
288         drag_id->from_parent = from_parent;
289       }
290       return;
291     }
292     if (GS(drag_id->id->name) != GS(id->name)) {
293       BLI_assert(!"All dragged IDs must have the same type");
294       return;
295     }
296   }
297 
298   /* Add to list. */
299   wmDragID *drag_id = MEM_callocN(sizeof(wmDragID), __func__);
300   drag_id->id = id;
301   drag_id->from_parent = from_parent;
302   BLI_addtail(&drag->ids, drag_id);
303 }
304 
WM_drag_ID(const wmDrag * drag,short idcode)305 ID *WM_drag_ID(const wmDrag *drag, short idcode)
306 {
307   if (drag->type != WM_DRAG_ID) {
308     return NULL;
309   }
310 
311   wmDragID *drag_id = drag->ids.first;
312   if (!drag_id) {
313     return NULL;
314   }
315 
316   ID *id = drag_id->id;
317   return (idcode == 0 || GS(id->name) == idcode) ? id : NULL;
318 }
319 
WM_drag_ID_from_event(const wmEvent * event,short idcode)320 ID *WM_drag_ID_from_event(const wmEvent *event, short idcode)
321 {
322   if (event->custom != EVT_DATA_DRAGDROP) {
323     return NULL;
324   }
325 
326   ListBase *lb = event->customdata;
327   return WM_drag_ID(lb->first, idcode);
328 }
329 
330 /* ************** draw ***************** */
331 
wm_drop_operator_draw(const char * name,int x,int y)332 static void wm_drop_operator_draw(const char *name, int x, int y)
333 {
334   const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
335   const float col_fg[4] = {1.0f, 1.0f, 1.0f, 1.0f};
336   const float col_bg[4] = {0.0f, 0.0f, 0.0f, 0.2f};
337 
338   UI_fontstyle_draw_simple_backdrop(fstyle, x, y, name, col_fg, col_bg);
339 }
340 
wm_drag_name(wmDrag * drag)341 static const char *wm_drag_name(wmDrag *drag)
342 {
343   switch (drag->type) {
344     case WM_DRAG_ID: {
345       ID *id = WM_drag_ID(drag, 0);
346       bool single = (BLI_listbase_count_at_most(&drag->ids, 2) == 1);
347 
348       if (single) {
349         return id->name + 2;
350       }
351       if (id) {
352         return BKE_idtype_idcode_to_name_plural(GS(id->name));
353       }
354       break;
355     }
356     case WM_DRAG_PATH:
357     case WM_DRAG_NAME:
358       return drag->path;
359   }
360   return "";
361 }
362 
drag_rect_minmax(rcti * rect,int x1,int y1,int x2,int y2)363 static void drag_rect_minmax(rcti *rect, int x1, int y1, int x2, int y2)
364 {
365   if (rect->xmin > x1) {
366     rect->xmin = x1;
367   }
368   if (rect->xmax < x2) {
369     rect->xmax = x2;
370   }
371   if (rect->ymin > y1) {
372     rect->ymin = y1;
373   }
374   if (rect->ymax < y2) {
375     rect->ymax = y2;
376   }
377 }
378 
379 /* called in wm_draw.c */
380 /* if rect set, do not draw */
wm_drags_draw(bContext * C,wmWindow * win,rcti * rect)381 void wm_drags_draw(bContext *C, wmWindow *win, rcti *rect)
382 {
383   const uiFontStyle *fstyle = UI_FSTYLE_WIDGET;
384   wmWindowManager *wm = CTX_wm_manager(C);
385   const int winsize_y = WM_window_pixels_y(win);
386 
387   int cursorx = win->eventstate->x;
388   int cursory = win->eventstate->y;
389   if (rect) {
390     rect->xmin = rect->xmax = cursorx;
391     rect->ymin = rect->ymax = cursory;
392   }
393 
394   /* Should we support multi-line drag draws? Maybe not, more types mixed wont work well. */
395   GPU_blend(GPU_BLEND_ALPHA);
396   LISTBASE_FOREACH (wmDrag *, drag, &wm->drags) {
397     const uchar text_col[] = {255, 255, 255, 255};
398     int iconsize = UI_DPI_ICON_SIZE;
399     int padding = 4 * UI_DPI_FAC;
400 
401     /* image or icon */
402     int x, y;
403     if (drag->imb) {
404       x = cursorx - drag->sx / 2;
405       y = cursory - drag->sy / 2;
406 
407       if (rect) {
408         drag_rect_minmax(rect, x, y, x + drag->sx, y + drag->sy);
409       }
410       else {
411         float col[4] = {1.0f, 1.0f, 1.0f, 0.65f}; /* this blends texture */
412         IMMDrawPixelsTexState state = immDrawPixelsTexSetup(GPU_SHADER_2D_IMAGE_COLOR);
413         immDrawPixelsTexScaled(&state,
414                                x,
415                                y,
416                                drag->imb->x,
417                                drag->imb->y,
418                                GPU_RGBA8,
419                                false,
420                                drag->imb->rect,
421                                drag->scale,
422                                drag->scale,
423                                1.0f,
424                                1.0f,
425                                col);
426       }
427     }
428     else {
429       x = cursorx - 2 * padding;
430       y = cursory - 2 * UI_DPI_FAC;
431 
432       if (rect) {
433         drag_rect_minmax(rect, x, y, x + iconsize, y + iconsize);
434       }
435       else {
436         UI_icon_draw_ex(x, y, drag->icon, U.inv_dpi_fac, 0.8, 0.0f, text_col, false);
437       }
438     }
439 
440     /* item name */
441     if (drag->imb) {
442       x = cursorx - drag->sx / 2;
443       y = cursory - drag->sy / 2 - iconsize;
444     }
445     else {
446       x = cursorx + 10 * UI_DPI_FAC;
447       y = cursory + 1 * UI_DPI_FAC;
448     }
449 
450     if (rect) {
451       int w = UI_fontstyle_string_width(fstyle, wm_drag_name(drag));
452       drag_rect_minmax(rect, x, y, x + w, y + iconsize);
453     }
454     else {
455       UI_fontstyle_draw_simple(fstyle, x, y, wm_drag_name(drag), text_col);
456     }
457 
458     /* operator name with roundbox */
459     if (drag->opname[0]) {
460       if (drag->imb) {
461         x = cursorx - drag->sx / 2;
462 
463         if (cursory + drag->sy / 2 + padding + iconsize < winsize_y) {
464           y = cursory + drag->sy / 2 + padding;
465         }
466         else {
467           y = cursory - drag->sy / 2 - padding - iconsize - padding - iconsize;
468         }
469       }
470       else {
471         x = cursorx - 2 * padding;
472 
473         if (cursory + iconsize + iconsize < winsize_y) {
474           y = (cursory + iconsize) + padding;
475         }
476         else {
477           y = (cursory - iconsize) - padding;
478         }
479       }
480 
481       if (rect) {
482         int w = UI_fontstyle_string_width(fstyle, wm_drag_name(drag));
483         drag_rect_minmax(rect, x, y, x + w, y + iconsize);
484       }
485       else {
486         wm_drop_operator_draw(drag->opname, x, y);
487       }
488     }
489   }
490   GPU_blend(GPU_BLEND_NONE);
491 }
492