1 | /* GTK - The GIMP Toolkit |
2 | * Copyright 2019 Matthias Clasen |
3 | * |
4 | * This library is free software; you can redistribute it and/or |
5 | * modify it under the terms of the GNU Lesser General Public |
6 | * License as published by the Free Software Foundation; either |
7 | * version 2 of the License, or (at your option) any later version. |
8 | * |
9 | * This library is distributed in the hope that it will be useful, |
10 | * but WITHOUT ANY WARRANTY; without even the implied warranty of |
11 | * MERCHANTABILITY or FITNESS FOR A PARTICULAR PURPOSE. See the GNU |
12 | * Lesser General Public License for more details. |
13 | * |
14 | * You should have received a copy of the GNU Lesser General Public |
15 | * License along with this library. If not, see <http://www.gnu.org/licenses/>. |
16 | */ |
17 | |
18 | #include "config.h" |
19 | |
20 | #include "gtkdragicon.h" |
21 | |
22 | #include "gtkintl.h" |
23 | #include "gtkwidgetprivate.h" |
24 | #include "gtkcssstyleprivate.h" |
25 | #include "gtkcsstypesprivate.h" |
26 | #include "gtknativeprivate.h" |
27 | #include "gtkpicture.h" |
28 | #include "gtkcssboxesimplprivate.h" |
29 | #include "gtkcssnumbervalueprivate.h" |
30 | |
31 | #include "gdk/gdksurfaceprivate.h" |
32 | |
33 | /* for the drag icons */ |
34 | #include "gtkcolorswatchprivate.h" |
35 | #include "gtkimage.h" |
36 | #include "gtklabel.h" |
37 | #include "gtkrendernodepaintableprivate.h" |
38 | #include "gtktextutil.h" |
39 | |
40 | |
41 | /** |
42 | * GtkDragIcon: |
43 | * |
44 | * `GtkDragIcon` is a `GtkRoot` implementation for drag icons. |
45 | * |
46 | * A drag icon moves with the pointer during a Drag-and-Drop operation |
47 | * and is destroyed when the drag ends. |
48 | * |
49 | * To set up a drag icon and associate it with an ongoing drag operation, |
50 | * use [func@Gtk.DragIcon.get_for_drag] to get the icon for a drag. You can |
51 | * then use it like any other widget and use [method@Gtk.DragIcon.set_child] |
52 | * to set whatever widget should be used for the drag icon. |
53 | * |
54 | * Keep in mind that drag icons do not allow user input. |
55 | */ |
56 | struct _GtkDragIcon |
57 | { |
58 | GtkWidget parent_instance; |
59 | |
60 | GdkSurface *surface; |
61 | GskRenderer *renderer; |
62 | GtkWidget *child; |
63 | }; |
64 | |
65 | struct _GtkDragIconClass |
66 | { |
67 | GtkWidgetClass parent_class; |
68 | }; |
69 | |
70 | enum { |
71 | PROP_0, |
72 | PROP_CHILD, |
73 | |
74 | LAST_ARG |
75 | }; |
76 | |
77 | static GParamSpec *properties[LAST_ARG] = { NULL, }; |
78 | |
79 | static void gtk_drag_icon_root_init (GtkRootInterface *iface); |
80 | static void gtk_drag_icon_native_init (GtkNativeInterface *iface); |
81 | |
82 | G_DEFINE_TYPE_WITH_CODE (GtkDragIcon, gtk_drag_icon, GTK_TYPE_WIDGET, |
83 | G_IMPLEMENT_INTERFACE (GTK_TYPE_NATIVE, |
84 | gtk_drag_icon_native_init) |
85 | G_IMPLEMENT_INTERFACE (GTK_TYPE_ROOT, |
86 | gtk_drag_icon_root_init)) |
87 | |
88 | static GdkDisplay * |
89 | gtk_drag_icon_root_get_display (GtkRoot *self) |
90 | { |
91 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: self); |
92 | |
93 | if (icon->surface) |
94 | return gdk_surface_get_display (surface: icon->surface); |
95 | |
96 | return gdk_display_get_default (); |
97 | } |
98 | |
99 | static void |
100 | gtk_drag_icon_root_init (GtkRootInterface *iface) |
101 | { |
102 | iface->get_display = gtk_drag_icon_root_get_display; |
103 | } |
104 | |
105 | static GdkSurface * |
106 | gtk_drag_icon_native_get_surface (GtkNative *native) |
107 | { |
108 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: native); |
109 | |
110 | return icon->surface; |
111 | } |
112 | |
113 | static GskRenderer * |
114 | gtk_drag_icon_native_get_renderer (GtkNative *native) |
115 | { |
116 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: native); |
117 | |
118 | return icon->renderer; |
119 | } |
120 | |
121 | static void |
122 | gtk_drag_icon_native_get_surface_transform (GtkNative *native, |
123 | double *x, |
124 | double *y) |
125 | { |
126 | GtkCssBoxes css_boxes; |
127 | const graphene_rect_t *margin_rect; |
128 | |
129 | gtk_css_boxes_init (boxes: &css_boxes, GTK_WIDGET (native)); |
130 | margin_rect = gtk_css_boxes_get_margin_rect (boxes: &css_boxes); |
131 | |
132 | *x = - margin_rect->origin.x; |
133 | *y = - margin_rect->origin.y; |
134 | } |
135 | |
136 | static void |
137 | gtk_drag_icon_move_resize (GtkDragIcon *icon) |
138 | { |
139 | GtkRequisition req; |
140 | |
141 | if (icon->surface) |
142 | { |
143 | gtk_widget_get_preferred_size (GTK_WIDGET (icon), NULL, natural_size: &req); |
144 | gdk_drag_surface_present (drag_surface: GDK_DRAG_SURFACE (ptr: icon->surface), |
145 | MAX (1, req.width), |
146 | MAX (1, req.height)); |
147 | } |
148 | } |
149 | |
150 | static void |
151 | gtk_drag_icon_present (GtkDragIcon *icon) |
152 | { |
153 | GtkWidget *widget = GTK_WIDGET (icon); |
154 | |
155 | if (!_gtk_widget_get_alloc_needed (widget)) |
156 | gtk_widget_ensure_allocate (widget); |
157 | else if (gtk_widget_get_visible (widget)) |
158 | gtk_drag_icon_move_resize (icon); |
159 | } |
160 | |
161 | static void |
162 | gtk_drag_icon_native_layout (GtkNative *native, |
163 | int width, |
164 | int height) |
165 | { |
166 | gtk_widget_allocate (GTK_WIDGET (native), width, height, baseline: -1, NULL); |
167 | } |
168 | |
169 | static void |
170 | gtk_drag_icon_native_init (GtkNativeInterface *iface) |
171 | { |
172 | iface->get_surface = gtk_drag_icon_native_get_surface; |
173 | iface->get_renderer = gtk_drag_icon_native_get_renderer; |
174 | iface->get_surface_transform = gtk_drag_icon_native_get_surface_transform; |
175 | iface->layout = gtk_drag_icon_native_layout; |
176 | } |
177 | |
178 | static gboolean |
179 | surface_render (GdkSurface *surface, |
180 | cairo_region_t *region, |
181 | GtkWidget *widget) |
182 | { |
183 | gtk_widget_render (widget, surface, region); |
184 | return TRUE; |
185 | } |
186 | |
187 | static void |
188 | gtk_drag_icon_realize (GtkWidget *widget) |
189 | { |
190 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget); |
191 | |
192 | g_warn_if_fail (icon->surface != NULL); |
193 | |
194 | gdk_surface_set_widget (self: icon->surface, widget); |
195 | |
196 | g_signal_connect (icon->surface, "render" , G_CALLBACK (surface_render), widget); |
197 | |
198 | GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->realize (widget); |
199 | |
200 | icon->renderer = gsk_renderer_new_for_surface (surface: icon->surface); |
201 | |
202 | gtk_native_realize (self: GTK_NATIVE (ptr: icon)); |
203 | } |
204 | |
205 | static void |
206 | gtk_drag_icon_unrealize (GtkWidget *widget) |
207 | { |
208 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget); |
209 | |
210 | gtk_native_unrealize (self: GTK_NATIVE (ptr: icon)); |
211 | |
212 | GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->unrealize (widget); |
213 | |
214 | gsk_renderer_unrealize (renderer: icon->renderer); |
215 | g_clear_object (&icon->renderer); |
216 | |
217 | if (icon->surface) |
218 | { |
219 | g_signal_handlers_disconnect_by_func (icon->surface, surface_render, widget); |
220 | gdk_surface_set_widget (self: icon->surface, NULL); |
221 | } |
222 | } |
223 | |
224 | static void |
225 | gtk_drag_icon_map (GtkWidget *widget) |
226 | { |
227 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget); |
228 | |
229 | gtk_drag_icon_move_resize (icon); |
230 | |
231 | GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->map (widget); |
232 | |
233 | if (icon->child && gtk_widget_get_visible (widget: icon->child)) |
234 | gtk_widget_map (widget: icon->child); |
235 | } |
236 | |
237 | static void |
238 | gtk_drag_icon_unmap (GtkWidget *widget) |
239 | { |
240 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget); |
241 | |
242 | g_warn_if_fail (icon->surface != NULL); |
243 | GTK_WIDGET_CLASS (gtk_drag_icon_parent_class)->unmap (widget); |
244 | if (icon->surface) |
245 | gdk_surface_hide (surface: icon->surface); |
246 | |
247 | if (icon->child) |
248 | gtk_widget_unmap (widget: icon->child); |
249 | } |
250 | |
251 | static void |
252 | gtk_drag_icon_measure (GtkWidget *widget, |
253 | GtkOrientation orientation, |
254 | int for_size, |
255 | int *minimum, |
256 | int *natural, |
257 | int *minimum_baseline, |
258 | int *natural_baseline) |
259 | { |
260 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget); |
261 | |
262 | if (icon->child) |
263 | gtk_widget_measure (widget: icon->child, |
264 | orientation, for_size, |
265 | minimum, natural, |
266 | minimum_baseline, natural_baseline); |
267 | } |
268 | |
269 | static void |
270 | gtk_drag_icon_size_allocate (GtkWidget *widget, |
271 | int width, |
272 | int height, |
273 | int baseline) |
274 | { |
275 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: widget); |
276 | |
277 | if (icon->child) |
278 | gtk_widget_allocate (widget: icon->child, width, height, baseline, NULL); |
279 | } |
280 | |
281 | static void |
282 | gtk_drag_icon_show (GtkWidget *widget) |
283 | { |
284 | _gtk_widget_set_visible_flag (widget, TRUE); |
285 | gtk_css_node_validate (cssnode: gtk_widget_get_css_node (widget)); |
286 | gtk_widget_realize (widget); |
287 | gtk_drag_icon_present (icon: GTK_DRAG_ICON (ptr: widget)); |
288 | gtk_widget_map (widget); |
289 | } |
290 | |
291 | static void |
292 | gtk_drag_icon_hide (GtkWidget *widget) |
293 | { |
294 | _gtk_widget_set_visible_flag (widget, FALSE); |
295 | gtk_widget_unmap (widget); |
296 | } |
297 | |
298 | static void |
299 | gtk_drag_icon_dispose (GObject *object) |
300 | { |
301 | GtkDragIcon *icon = GTK_DRAG_ICON (ptr: object); |
302 | |
303 | g_clear_pointer (&icon->child, gtk_widget_unparent); |
304 | |
305 | G_OBJECT_CLASS (gtk_drag_icon_parent_class)->dispose (object); |
306 | |
307 | g_clear_object (&icon->surface); |
308 | } |
309 | |
310 | static void |
311 | gtk_drag_icon_get_property (GObject *object, |
312 | guint prop_id, |
313 | GValue *value, |
314 | GParamSpec *pspec) |
315 | { |
316 | GtkDragIcon *self = GTK_DRAG_ICON (ptr: object); |
317 | |
318 | switch (prop_id) |
319 | { |
320 | case PROP_CHILD: |
321 | g_value_set_object (value, v_object: self->child); |
322 | break; |
323 | |
324 | default: |
325 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
326 | break; |
327 | } |
328 | } |
329 | |
330 | static void |
331 | gtk_drag_icon_set_property (GObject *object, |
332 | guint prop_id, |
333 | const GValue *value, |
334 | GParamSpec *pspec) |
335 | { |
336 | GtkDragIcon *self = GTK_DRAG_ICON (ptr: object); |
337 | |
338 | switch (prop_id) |
339 | { |
340 | case PROP_CHILD: |
341 | gtk_drag_icon_set_child (self, child: g_value_get_object (value)); |
342 | break; |
343 | |
344 | default: |
345 | G_OBJECT_WARN_INVALID_PROPERTY_ID (object, prop_id, pspec); |
346 | break; |
347 | } |
348 | } |
349 | |
350 | static void |
351 | gtk_drag_icon_class_init (GtkDragIconClass *klass) |
352 | { |
353 | GObjectClass *object_class = G_OBJECT_CLASS (klass); |
354 | GtkWidgetClass *widget_class = GTK_WIDGET_CLASS (klass); |
355 | |
356 | object_class->dispose = gtk_drag_icon_dispose; |
357 | object_class->get_property = gtk_drag_icon_get_property; |
358 | object_class->set_property = gtk_drag_icon_set_property; |
359 | |
360 | widget_class->realize = gtk_drag_icon_realize; |
361 | widget_class->unrealize = gtk_drag_icon_unrealize; |
362 | widget_class->map = gtk_drag_icon_map; |
363 | widget_class->unmap = gtk_drag_icon_unmap; |
364 | widget_class->measure = gtk_drag_icon_measure; |
365 | widget_class->size_allocate = gtk_drag_icon_size_allocate; |
366 | widget_class->show = gtk_drag_icon_show; |
367 | widget_class->hide = gtk_drag_icon_hide; |
368 | |
369 | /** |
370 | * GtkDragIcon:child: (attributes org.gtk.Property.get=gtk_drag_icon_get_child org.gtk.Property.set=gtk_drag_icon_set_child) |
371 | * |
372 | * The widget to display as drag icon. |
373 | */ |
374 | properties[PROP_CHILD] = |
375 | g_param_spec_object (name: "child" , |
376 | P_("Child" ), |
377 | P_("The widget to display as drag icon." ), |
378 | GTK_TYPE_WIDGET, |
379 | flags: G_PARAM_READWRITE | G_PARAM_EXPLICIT_NOTIFY | G_PARAM_STATIC_STRINGS); |
380 | |
381 | g_object_class_install_properties (oclass: object_class, n_pspecs: LAST_ARG, pspecs: properties); |
382 | |
383 | gtk_widget_class_set_css_name (widget_class, name: "dnd" ); |
384 | } |
385 | |
386 | static void |
387 | gtk_drag_icon_init (GtkDragIcon *self) |
388 | { |
389 | gtk_widget_set_can_target (GTK_WIDGET (self), FALSE); |
390 | } |
391 | |
392 | /** |
393 | * gtk_drag_icon_get_for_drag: |
394 | * @drag: a `GdkDrag` |
395 | * |
396 | * Gets the `GtkDragIcon` in use with @drag. |
397 | * |
398 | * If no drag icon exists yet, a new one will be created |
399 | * and shown. |
400 | * |
401 | * Returns: (transfer none): the `GtkDragIcon` |
402 | */ |
403 | GtkWidget * |
404 | gtk_drag_icon_get_for_drag (GdkDrag *drag) |
405 | { |
406 | static GQuark drag_icon_quark = 0; |
407 | GtkWidget *self; |
408 | |
409 | g_return_val_if_fail (GDK_IS_DRAG (drag), NULL); |
410 | |
411 | if (G_UNLIKELY (drag_icon_quark == 0)) |
412 | drag_icon_quark = g_quark_from_static_string (string: "-gtk-drag-icon" ); |
413 | |
414 | self = g_object_get_qdata (G_OBJECT (drag), quark: drag_icon_quark); |
415 | if (self == NULL) |
416 | { |
417 | self = g_object_new (GTK_TYPE_DRAG_ICON, NULL); |
418 | |
419 | GTK_DRAG_ICON (ptr: self)->surface = g_object_ref (gdk_drag_get_drag_surface (drag)); |
420 | |
421 | g_object_set_qdata_full (G_OBJECT (drag), quark: drag_icon_quark, g_object_ref_sink (self), destroy: g_object_unref); |
422 | |
423 | if (GTK_DRAG_ICON (ptr: self)->child != NULL) |
424 | gtk_widget_show (widget: self); |
425 | } |
426 | |
427 | return self; |
428 | } |
429 | |
430 | /** |
431 | * gtk_drag_icon_set_from_paintable: |
432 | * @drag: a `GdkDrag` |
433 | * @paintable: a `GdkPaintable` to display |
434 | * @hot_x: X coordinate of the hotspot |
435 | * @hot_y: Y coordinate of the hotspot |
436 | * |
437 | * Creates a `GtkDragIcon` that shows @paintable, and associates |
438 | * it with the drag operation. |
439 | * |
440 | * The hotspot position on the paintable is aligned with the |
441 | * hotspot of the cursor. |
442 | */ |
443 | void |
444 | gtk_drag_icon_set_from_paintable (GdkDrag *drag, |
445 | GdkPaintable *paintable, |
446 | int hot_x, |
447 | int hot_y) |
448 | { |
449 | GtkWidget *icon; |
450 | GtkWidget *picture; |
451 | |
452 | gdk_drag_set_hotspot (drag, hot_x, hot_y); |
453 | |
454 | icon = gtk_drag_icon_get_for_drag (drag); |
455 | |
456 | picture = gtk_picture_new_for_paintable (paintable); |
457 | gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: picture), FALSE); |
458 | gtk_drag_icon_set_child (self: GTK_DRAG_ICON (ptr: icon), child: picture); |
459 | } |
460 | |
461 | /** |
462 | * gtk_drag_icon_set_child: (attributes org.gtk.Method.set_property=child) |
463 | * @self: a `GtkDragIcon` |
464 | * @child: (nullable): a `GtkWidget` |
465 | * |
466 | * Sets the widget to display as the drag icon. |
467 | */ |
468 | void |
469 | gtk_drag_icon_set_child (GtkDragIcon *self, |
470 | GtkWidget *child) |
471 | { |
472 | g_return_if_fail (GTK_IS_DRAG_ICON (self)); |
473 | g_return_if_fail (child == NULL || GTK_IS_WIDGET (child)); |
474 | |
475 | if (self->child == child) |
476 | return; |
477 | |
478 | if (self->child) |
479 | gtk_widget_unparent (widget: self->child); |
480 | |
481 | self->child = child; |
482 | |
483 | if (self->child) |
484 | { |
485 | gtk_widget_set_parent (widget: self->child, GTK_WIDGET (self)); |
486 | gtk_widget_show (GTK_WIDGET (self)); |
487 | } |
488 | |
489 | g_object_notify_by_pspec (G_OBJECT (self), pspec: properties[PROP_CHILD]); |
490 | } |
491 | |
492 | /** |
493 | * gtk_drag_icon_get_child: (attributes org.gtk.Method.get_property=child) |
494 | * @self: a `GtkDragIcon` |
495 | * |
496 | * Gets the widget currently used as drag icon. |
497 | * |
498 | * Returns: (nullable) (transfer none): The drag icon |
499 | **/ |
500 | GtkWidget * |
501 | gtk_drag_icon_get_child (GtkDragIcon *self) |
502 | { |
503 | g_return_val_if_fail (GTK_IS_DRAG_ICON (self), NULL); |
504 | |
505 | return self->child; |
506 | } |
507 | |
508 | /** |
509 | * gtk_drag_icon_create_widget_for_value: |
510 | * @value: a `GValue` |
511 | * |
512 | * Creates a widget that can be used as a drag icon for the given |
513 | * @value. |
514 | * |
515 | * Supported types include strings, `GdkRGBA` and `GtkTextBuffer`. |
516 | * If GTK does not know how to create a widget for a given value, |
517 | * it will return %NULL. |
518 | * |
519 | * This method is used to set the default drag icon on drag-and-drop |
520 | * operations started by `GtkDragSource`, so you don't need to set |
521 | * a drag icon using this function there. |
522 | * |
523 | * Returns: (nullable) (transfer full): A new `GtkWidget` |
524 | * for displaying @value as a drag icon. |
525 | */ |
526 | GtkWidget * |
527 | gtk_drag_icon_create_widget_for_value (const GValue *value) |
528 | { |
529 | g_return_val_if_fail (G_IS_VALUE (value), NULL); |
530 | |
531 | if (G_VALUE_HOLDS (value, G_TYPE_STRING)) |
532 | { |
533 | return gtk_label_new (str: g_value_get_string (value)); |
534 | } |
535 | else if (G_VALUE_HOLDS (value, GDK_TYPE_PAINTABLE)) |
536 | { |
537 | GtkWidget *image; |
538 | |
539 | image = gtk_image_new_from_paintable (paintable: g_value_get_object (value)); |
540 | gtk_widget_add_css_class (widget: image, css_class: "large-icons" ); |
541 | |
542 | return image; |
543 | } |
544 | else if (G_VALUE_HOLDS (value, GDK_TYPE_RGBA)) |
545 | { |
546 | GtkWidget *swatch; |
547 | |
548 | swatch = gtk_color_swatch_new (); |
549 | gtk_color_swatch_set_can_drag (GTK_COLOR_SWATCH (swatch), FALSE); |
550 | gtk_color_swatch_set_can_drop (GTK_COLOR_SWATCH (swatch), FALSE); |
551 | gtk_color_swatch_set_rgba (GTK_COLOR_SWATCH (swatch), color: g_value_get_boxed (value)); |
552 | |
553 | return swatch; |
554 | } |
555 | else if (G_VALUE_HOLDS (value, G_TYPE_FILE)) |
556 | { |
557 | GFileInfo *info; |
558 | GtkWidget *image; |
559 | |
560 | info = g_file_query_info (G_FILE (g_value_get_object (value)), attributes: "standard::icon" , flags: 0, NULL, NULL); |
561 | image = gtk_image_new_from_gicon (icon: g_file_info_get_icon (info)); |
562 | gtk_widget_add_css_class (widget: image, css_class: "large-icons" ); |
563 | g_object_unref (object: info); |
564 | |
565 | return image; |
566 | } |
567 | else if (G_VALUE_HOLDS (value, GTK_TYPE_TEXT_BUFFER)) |
568 | { |
569 | GtkTextBuffer *buffer = g_value_get_object (value); |
570 | GtkTextIter start, end; |
571 | GdkPaintable *paintable; |
572 | GtkWidget *picture; |
573 | |
574 | if (buffer == NULL || !gtk_text_buffer_get_selection_bounds (buffer, start: &start, end: &end)) |
575 | return NULL; |
576 | |
577 | picture = gtk_picture_new (); |
578 | paintable = gtk_text_util_create_rich_drag_icon (widget: picture, buffer, start: &start, end: &end); |
579 | gtk_picture_set_paintable (self: GTK_PICTURE (ptr: picture), paintable); |
580 | gtk_picture_set_can_shrink (self: GTK_PICTURE (ptr: picture), FALSE); |
581 | g_object_unref (object: paintable); |
582 | |
583 | return picture; |
584 | } |
585 | else if (G_VALUE_HOLDS (value, GSK_TYPE_RENDER_NODE)) |
586 | { |
587 | GskRenderNode *node; |
588 | GdkPaintable *paintable; |
589 | graphene_rect_t bounds; |
590 | GtkWidget *image; |
591 | |
592 | node = gsk_value_get_render_node (value); |
593 | if (node == NULL) |
594 | return NULL; |
595 | |
596 | gsk_render_node_get_bounds (node, bounds: &bounds); |
597 | paintable = gtk_render_node_paintable_new (node, bounds: &bounds); |
598 | image = gtk_image_new_from_paintable (paintable); |
599 | gtk_image_set_icon_size (GTK_IMAGE (image), icon_size: GTK_ICON_SIZE_LARGE); |
600 | g_object_unref (object: paintable); |
601 | |
602 | return image; |
603 | } |
604 | else |
605 | { |
606 | return NULL; |
607 | } |
608 | } |
609 | |
610 | |