diagram.c 41.5 KB
Newer Older
1
/* Dia -- an diagram creation/manipulation program
Alexander Larsson's avatar
Alexander Larsson committed
2 3 4 5 6 7 8 9 10 11 12 13 14
 * Copyright (C) 1998 Alexander Larsson
 *
 * 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
15 16
 * along with this program; if not, write to the Free Software
 * Foundation, Inc., 59 Temple Place - Suite 330, Boston, MA 02111-1307, USA.
Alexander Larsson's avatar
Alexander Larsson committed
17
 */
18

Lars Clausen's avatar
Lars Clausen committed
19
#include <config.h>
20

Alexander Larsson's avatar
Alexander Larsson committed
21 22 23
#include <assert.h>
#include <string.h>

24
#include "intl.h"
Alexander Larsson's avatar
Alexander Larsson committed
25
#include "diagram.h"
26
#include "object.h"
Alexander Larsson's avatar
Alexander Larsson committed
27 28 29 30
#include "group.h"
#include "object_ops.h"
#include "focus.h"
#include "message.h"
31
#include "menus.h"
32
#include "preferences.h"
33
#include "properties-dialog.h"
Alexander Larsson's avatar
Alexander Larsson committed
34
#include "cut_n_paste.h"
35
#include "layer_dialog.h"
36
#include "app_procs.h"
37
#include "dia_dirs.h"
38
#include "load_save.h"
39
#include "recent_files.h"
40
#include "dia-application.h"
Lars Clausen's avatar
Lars Clausen committed
41
#include "autosave.h"
42
#include "dynamic_refresh.h"
Lars Clausen's avatar
Lars Clausen committed
43
#include "textedit.h"
44
#include "lib/diamarshal.h"
45
#include "parent.h"
46
#include "diacontext.h"
Alexander Larsson's avatar
Alexander Larsson committed
47

48 49
static GList *open_diagrams = NULL;

50
struct _ObjectExtent
51 52
{
  DiaObject *object;
53
  Rectangle extent;
54 55
};

56
typedef struct _ObjectExtent ObjectExtent;
57

58
static gint diagram_parent_sort_cb(gconstpointer a, gconstpointer b);
59 60


61
static void diagram_class_init (DiagramClass *klass);
62
static gboolean diagram_init(Diagram *obj, const char *filename);
63
static void diagram_update_for_filename(Diagram *dia);
64

65 66 67 68 69
enum {
  REMOVED,
  LAST_SIGNAL
};

70
static guint diagram_signals[LAST_SIGNAL] = { 0, };
71 72 73 74 75 76 77 78 79 80 81 82 83 84 85 86 87 88 89 90 91 92
static gpointer parent_class = NULL;

GType
diagram_get_type (void)
{
  static GType object_type = 0;

  if (!object_type)
    {
      static const GTypeInfo object_info =
      {
        sizeof (DiagramClass),
        (GBaseInitFunc) NULL,
        (GBaseFinalizeFunc) NULL,
        (GClassInitFunc) diagram_class_init,
        NULL,           /* class_finalize */
        NULL,           /* class_data */
        sizeof (Diagram),
        0,              /* n_preallocs */
	NULL            /* init */
      };

93
      object_type = g_type_register_static (DIA_TYPE_DIAGRAM_DATA,
94 95 96
                                            "Diagram",
                                            &object_info, 0);
    }
Zander's avatar
Zander committed
97

98 99 100 101
  return object_type;
}

static void
102
diagram_dispose (GObject *object)
103 104 105 106
{
  Diagram *dia = DIA_DIAGRAM(object);

  assert(dia->displays==NULL);
Zander's avatar
Zander committed
107

108 109 110 111 112 113
  if (g_list_index(open_diagrams, dia) >= 0) {
    dia_diagram_remove(dia);

    open_diagrams = g_list_remove(open_diagrams, dia);
    layer_dialog_update_diagram_list();
  }
114

115 116 117
  if (dia->undo)
    undo_destroy(dia->undo);
  dia->undo = NULL;
Zander's avatar
Zander committed
118

119
  diagram_cleanup_autosave(dia);
120

121 122 123
  G_OBJECT_CLASS (parent_class)->dispose (object);
}
static void
Zander's avatar
Zander committed
124
diagram_finalize(GObject *object)
125 126 127 128
{
  Diagram *dia = DIA_DIAGRAM(object);

  assert(dia->displays==NULL);
Zander's avatar
Zander committed
129

130 131 132 133 134
  if (dia->filename)
    g_free(dia->filename);
  dia->filename = NULL;

  G_OBJECT_CLASS (parent_class)->finalize (object);
135 136
}

137 138 139 140 141
static void
_diagram_removed (Diagram* dia)
{
}

142 143 144 145 146 147 148
static void
diagram_class_init (DiagramClass *klass)
{
  GObjectClass *object_class = G_OBJECT_CLASS (klass);

  parent_class = g_type_class_peek_parent (klass);

149 150 151 152 153 154 155 156 157
  diagram_signals[REMOVED] =
    g_signal_new ("removed",
	          G_TYPE_FROM_CLASS (klass),
	          G_SIGNAL_RUN_FIRST,
	          G_STRUCT_OFFSET (DiagramClass, removed),
	          NULL, NULL,
	          dia_marshal_VOID__VOID,
		  G_TYPE_NONE, 0);

158
  klass->removed = _diagram_removed;
159

160
  object_class->finalize = diagram_finalize;
161
  object_class->dispose = diagram_dispose;
162 163
}

164 165 166 167 168
GList *
dia_open_diagrams(void)
{
  return open_diagrams;
}
Alexander Larsson's avatar
Alexander Larsson committed
169

170 171 172 173 174 175 176 177 178 179 180 181 182 183 184 185 186 187 188
static void
_object_add (Diagram   *dia,
	     Layer     *layer,
             DiaObject *obj,
	     gpointer   user_data)
{
  if (obj)
    object_add_updates(obj, dia);
}
static void
_object_remove(Diagram   *dia,
	       Layer     *layer,
               DiaObject *obj,
	       gpointer   user_data)
{
  if (obj)
    object_add_updates(obj, dia);
}

189
/** Initializes a diagram with standard info and sets it to be called
Zander's avatar
Zander committed
190
 * 'filename'.
191 192 193
 * Returns TRUE if everything went ok, FALSE otherwise.
 * Will return FALSE if filename is not a legal string in the current
 * encoding.
194
 */
195
static gboolean
196
diagram_init(Diagram *dia, const char *filename)
197
{
198
  gchar *newfilename = NULL;
199
  GError *error = NULL;
Zander's avatar
Zander committed
200

201 202 203
  dia->data = &dia->parent_instance; /* compatibility */

  dia->pagebreak_color = prefs.new_diagram.pagebreak_color;
Zander's avatar
Zander committed
204

205
  get_paper_info (&dia->data->paper, -1, &prefs.new_diagram);
206 207 208 209 210 211 212

  dia->grid.width_x = prefs.grid.x;
  dia->grid.width_y = prefs.grid.y;
  dia->grid.width_w = prefs.grid.w;
  dia->grid.hex_size = 1.0;
  dia->grid.colour = prefs.new_diagram.grid_color;
  dia->grid.hex = prefs.grid.hex;
213 214
  dia->grid.visible_x = prefs.grid.vis_x;
  dia->grid.visible_y = prefs.grid.vis_y;
215 216 217 218 219 220 221
  dia->grid.dynamic = prefs.grid.dynamic;
  dia->grid.major_lines = prefs.grid.major_lines;

  dia->guides.nhguides = 0;
  dia->guides.hguides = NULL;
  dia->guides.nvguides = 0;
  dia->guides.vguides = NULL;
222

Lars Clausen's avatar
Lars Clausen committed
223 224
  if (dia->filename != NULL)
    g_free(dia->filename);
Lars Clausen's avatar
Lars Clausen committed
225 226 227
  /* Make sure the filename is absolute */
  if (!g_path_is_absolute(filename)) {
    gchar *pwd = g_get_current_dir();
228 229

    newfilename = g_build_filename(pwd, filename, NULL);
Lars Clausen's avatar
Lars Clausen committed
230 231 232
    g_free(pwd);
    filename = newfilename;
  }
233
  /* All Diagram functions assumes filename in filesystem encoding */
Zander's avatar
Zander committed
234

235 236 237 238 239 240 241 242 243
  dia->filename = g_filename_to_utf8(filename, -1, NULL, NULL, &error);
  if (error != NULL) {
    message_error(_("Couldn't convert filename '%s' to UTF-8: %s\n"),
		  dia_message_filename(filename), error->message);
    g_error_free(error);
    dia->filename = g_strdup(_("Error"));
    return FALSE;
  }

244
  dia->unsaved = TRUE;
245
  dia->mollified = FALSE;
Lars Clausen's avatar
Lars Clausen committed
246
  dia->autosavefilename = NULL;
247 248 249 250 251 252 253 254

  if (dia->undo)
    undo_destroy(dia->undo);
  dia->undo = new_undo_stack(dia);

  if (!g_list_find(open_diagrams, dia))
    open_diagrams = g_list_prepend(open_diagrams, dia);

255 256
  if (app_is_interactive())
    layer_dialog_update_diagram_list();
257 258

  g_free(newfilename);
259 260 261 262

  g_signal_connect (G_OBJECT(dia), "object_add", G_CALLBACK(_object_add), dia);
  g_signal_connect (G_OBJECT(dia), "object_remove", G_CALLBACK(_object_remove), dia);

263
  return TRUE;
264 265 266 267 268 269 270
}

int
diagram_load_into(Diagram         *diagram,
		  const char      *filename,
		  DiaImportFilter *ifilter)
{
271 272 273
  /* ToDo: move context further up in the callstack and to sth useful with it's content */
  DiaContext *ctx = dia_context_new(_("Load Into"));

274
  gboolean was_default = diagram->is_default;
275 276
  if (!ifilter)
    ifilter = filter_guess_import_filter(filename);
277 278 279
  /* slightly hacked to avoid 'Not a Dia File' for .shape */
  if (!ifilter && g_str_has_suffix (filename, ".shape"))
    ifilter = filter_import_get_by_name ("dia-svg");
280 281
  if (!ifilter)  /* default to native format */
    ifilter = &dia_import_filter;
282

283 284
  dia_context_set_filename (ctx, filename);
  if (ifilter->import_func(filename, diagram->data, ctx, ifilter->user_data)) {
285 286 287
    if (ifilter != &dia_import_filter) {
      /* When loading non-Dia files, change filename to reflect that saving
       * will produce a Dia file. See bug #440093 */
288 289 290 291 292 293 294 295 296 297 298 299 300 301 302 303 304 305
      if (strcmp (diagram->filename, filename) == 0) {
	/* not a real load into but initial load */
	gchar *old_filename = g_strdup (diagram->filename);
        gchar *suffix_offset = g_utf8_strrchr(old_filename, -1, (gunichar)'.');
        gchar *new_filename;
        if (suffix_offset != NULL) {
	  new_filename = g_strndup(old_filename, suffix_offset - old_filename);
	  g_free(old_filename);
	} else {
	  new_filename = old_filename;
	}
        old_filename = g_strconcat(new_filename, ".dia", NULL);
        g_free(new_filename);
        diagram_set_filename(diagram, old_filename);
        g_free(old_filename);
        diagram->unsaved = TRUE;
	diagram_update_for_filename (diagram);
	diagram_modified(diagram);
306
      }
307 308
    } else {
      /* the initial diagram should have confirmed filename  */
Zander's avatar
Zander committed
309
      diagram->unsaved =
310
	  strcmp(filename, diagram->filename) == 0 ? FALSE : was_default;
311
    }
312
    diagram_set_modified(diagram, TRUE);
313
    dia_context_release(ctx);
314
    return TRUE;
315 316
  } else {
    dia_context_release(ctx);
317
    return FALSE;
318
  }
319 320 321 322 323
}

Diagram *
diagram_load(const char *filename, DiaImportFilter *ifilter)
{
324 325
  Diagram *diagram = NULL;
  GList *diagrams;
326
  gboolean was_default = FALSE;
327 328 329

  for (diagrams = open_diagrams; diagrams != NULL; diagrams = g_list_next(diagrams)) {
    Diagram *old_diagram = (Diagram*)diagrams->data;
330
    if (old_diagram->is_default) {
331
      diagram = old_diagram;
332
      was_default = TRUE;
333
      break;
Zander's avatar
Zander committed
334
    }
335
  }
336

337
  /* TODO: Make diagram not be initialized twice */
338 339 340
  if (diagram == NULL) {
    diagram = new_diagram(filename);
  }
341
  if (diagram == NULL) return NULL;
342

343 344
  if (   !diagram_init(diagram, filename)
      || !diagram_load_into (diagram, filename, ifilter)) {
345 346
    if (!was_default) /* don't kill the default diagram on import failure */
      diagram_destroy(diagram);
347
    diagram = NULL;
348
  } else {
349
    /* not modifying 'diagram->unsaved' the state depends on the import filter used */
350
    diagram_set_modified(diagram, FALSE);
351
    if (app_is_interactive()) {
352
      recent_file_history_add(filename);
353 354
      if (was_default) /* replacing it is like first remove than add */
        dia_diagram_remove(diagram);
355
      dia_diagram_add(diagram);
356
    }
357
  }
Zander's avatar
Zander committed
358

359
  if (diagram != NULL && was_default && app_is_interactive()) {
360
    diagram_update_for_filename(diagram);
361
    diagram->is_default = FALSE;
362 363
    if ( g_slist_length(diagram->displays) == 1 )
      display_set_active (diagram->displays->data);
364
  }
Zander's avatar
Zander committed
365

366 367 368
  return diagram;
}

369 370 371 372
/** Create a new diagram with the given filename.
 * If the diagram could not be created, e.g. because the filename is not
 * a legal string in the current encoding, return NULL.
 */
Alexander Larsson's avatar
Alexander Larsson committed
373
Diagram *
374
new_diagram(const char *filename)  /* Note: filename is copied */
Alexander Larsson's avatar
Alexander Larsson committed
375
{
376
  Diagram *dia = g_object_new(DIA_TYPE_DIAGRAM, NULL);
Alexander Larsson's avatar
Alexander Larsson committed
377

378 379 380
  if (diagram_init(dia, filename)) {
    return dia;
  } else {
381
    g_object_unref(dia);
382 383
    return NULL;
  }
Alexander Larsson's avatar
Alexander Larsson committed
384 385 386 387 388
}

void
diagram_destroy(Diagram *dia)
{
389
  g_signal_emit (dia, diagram_signals[REMOVED], 0);
390
  g_object_unref(dia);
Alexander Larsson's avatar
Alexander Larsson committed
391 392
}

393 394 395 396 397
/** Returns true if we consider the diagram modified.
 */
gboolean
diagram_is_modified(Diagram *dia)
{
398
  return dia->mollified || !undo_is_saved(dia->undo);
399 400
}

401 402 403
/** We might just have change the diagrams modified status.
 * This doesn't set the status, but merely updates the display.
 */
Alexander Larsson's avatar
Alexander Larsson committed
404 405 406
void
diagram_modified(Diagram *dia)
{
407
  GSList *displays;
408
  gchar *dia_name = diagram_get_name(dia);
Zander's avatar
Zander committed
409
  gchar *extra = g_path_get_dirname (dia->filename);
410
  gchar *title = g_strdup_printf ("%s%s (%s)", diagram_is_modified(dia) ? "*" : "", dia_name, extra ? extra : " ");
411

412
  g_free (dia_name);
413
  g_free (extra);
414
  displays = dia->displays;
415 416 417 418
  while (displays!=NULL) {
    DDisplay *ddisp = (DDisplay *) displays->data;

    ddisplay_set_title(ddisp, title);
Zander's avatar
Zander committed
419

420 421
    displays = g_slist_next(displays);
  }
422 423
  if (diagram_is_modified(dia)) {
    dia->autosaved = FALSE;
424
    dia->is_default = FALSE;
425
  }
426
  /*  diagram_set_modified(dia, TRUE);*/
427
  g_free (title);
428 429
}

430 431 432 433
/** Set this diagram explicitly modified.  This should not be called
 * by things that change the undo stack, as those modifications are
 * noticed through changes in the undo stack.
 */
434 435 436
void
diagram_set_modified(Diagram *dia, int modified)
{
437
  if (dia->mollified != modified)
438
  {
439
    dia->mollified = modified;
440
  }
441
  diagram_modified(dia);
Alexander Larsson's avatar
Alexander Larsson committed
442
}
443

Lars Clausen's avatar
Lars Clausen committed
444 445 446 447 448 449 450 451 452 453 454 455
/* ************ Functions that check for menu sensitivity ********* */

/* Suggested optimization: The loops take a lot of the time.  Collapse them
 * into one, have them set flags for the things that have been found true.
 * Harder to maintain.
 */
static gboolean
diagram_selected_any_groups(Diagram *dia) {
  GList *selected;

  for (selected = dia->data->selected;
       selected != NULL; selected = selected->next) {
456
    DiaObject *obj = (DiaObject*)selected->data;
Lars Clausen's avatar
Lars Clausen committed
457 458 459 460 461 462 463 464 465 466 467
    if (IS_GROUP(obj)) return TRUE;
  }
  return FALSE;
}

static gboolean
diagram_selected_any_parents(Diagram *dia) {
  GList *selected;

  for (selected = dia->data->selected;
       selected != NULL; selected = selected->next) {
468
    DiaObject *obj = (DiaObject*)selected->data;
Zander's avatar
Zander committed
469
    if (object_flags_set(obj, DIA_OBJECT_CAN_PARENT) && obj->children != NULL)
470
      return TRUE;
Lars Clausen's avatar
Lars Clausen committed
471 472 473 474 475 476 477 478 479 480
  }
  return FALSE;
}

static gboolean
diagram_selected_any_children(Diagram *dia) {
  GList *selected;

  for (selected = dia->data->selected;
       selected != NULL; selected = selected->next) {
481
    DiaObject *obj = (DiaObject*)selected->data;
Lars Clausen's avatar
Lars Clausen committed
482 483 484 485 486 487 488 489 490 491 492 493 494 495 496
    if (obj->parent != NULL) return TRUE;
  }
  return FALSE;
}

/** This is slightly more complex -- must see if any non-parented objects
 * are within a parenting object.
 */
static gboolean
diagram_selected_can_parent(Diagram *dia) {
  GList *selected;
  GList *parents = NULL;

  for (selected = dia->data->selected;
       selected != NULL; selected = selected->next) {
497
    DiaObject *obj = (DiaObject*)selected->data;
498
    if (object_flags_set(obj, DIA_OBJECT_CAN_PARENT)) {
Lars Clausen's avatar
Lars Clausen committed
499 500 501 502 503
      parents = g_list_prepend(parents, obj);
    }
  }
  for (selected = dia->data->selected;
       selected != NULL; selected = selected->next) {
504
    DiaObject *obj = (DiaObject*)selected->data;
Lars Clausen's avatar
Lars Clausen committed
505 506 507
    if (obj->parent == NULL) {
      GList *parent_tmp;
      for (parent_tmp = parents; parent_tmp != NULL; parent_tmp = parent_tmp->next) {
508
	if (object_within_parent(obj, (DiaObject*)parent_tmp->data)) {
Lars Clausen's avatar
Lars Clausen committed
509 510 511 512 513 514 515 516 517
	  g_list_free(parents);
	  return TRUE;
	}
      }
    }
  }
  g_list_free(parents);
  return FALSE;
}
518

Zander's avatar
Zander committed
519
/** Returns TRUE if an object is fully enclosed by a another object, which
520 521 522 523 524 525 526
 * can be a parent */
gboolean
object_within_parent(DiaObject *obj, DiaObject *p)
{
  Rectangle obj_bb = obj->bounding_box;
  if (!object_flags_set(p, DIA_OBJECT_CAN_PARENT))
    return FALSE;
Zander's avatar
Zander committed
527
  if (p == obj)
528 529 530 531 532 533 534 535 536
    return FALSE;
  if (obj_bb.left > p->bounding_box.left &&
      obj_bb.right < p->bounding_box.right &&
      obj_bb.top > p->bounding_box.top &&
      obj_bb.bottom < p->bounding_box.bottom)
    return TRUE;
  return FALSE;
}

537 538 539 540
/*
  This is the real implementation of the sensitivity update.
  TODO: move it to the DDisplay as it belongs to it IMHO
 */
Zander's avatar
Zander committed
541
void
542
diagram_update_menu_sensitivity (Diagram *dia)
543
{
544
  gint selected_count = dia ? g_list_length (dia->data->selected) : 0;
545
  DDisplay *ddisp = ddisplay_active();
546
  gboolean focus_active = dia ? (get_active_focus(dia->data) != NULL) : FALSE;
547
  gboolean textedit_active = ddisp ? textedit_mode(ddisp) : FALSE;
548
  GtkAction *action;
549

Lars Clausen's avatar
Lars Clausen committed
550
  /* Edit menu */
551 552 553 554 555 556 557 558 559 560 561 562 563 564 565 566 567 568 569 570 571 572
  if ((action = menus_get_action ("EditUndo")) != NULL)
    gtk_action_set_sensitive (action, dia ? undo_available(dia->undo, TRUE) : FALSE);
  if ((action = menus_get_action ("EditRedo")) != NULL)
    gtk_action_set_sensitive (action, dia ? undo_available(dia->undo, FALSE) : FALSE);
  if ((action = menus_get_action ("EditCopy")) != NULL)
    gtk_action_set_sensitive (action, textedit_active || selected_count > 0);
  if ((action = menus_get_action ("EditCut")) != NULL)
    gtk_action_set_sensitive (action, textedit_mode(ddisp) || selected_count > 0);
  if ((action = menus_get_action ("EditPaste")) != NULL)
    gtk_action_set_sensitive (action, textedit_active || cnp_exist_stored_objects());
  if ((action = menus_get_action ("EditDelete")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);
  if ((action = menus_get_action ("EditDuplicate")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);

  if ((action = menus_get_action ("EditCopytext")) != NULL)
    gtk_action_set_sensitive (action, focus_active);
  if ((action = menus_get_action ("EditCuttext")) != NULL)
    gtk_action_set_sensitive (action, focus_active);
  if ((action = menus_get_action ("EditPastetext")) != NULL)
    gtk_action_set_sensitive (action, focus_active);

Lars Clausen's avatar
Lars Clausen committed
573
  /* Objects menu */
574 575 576 577 578 579 580 581 582 583 584 585 586 587 588 589 590
  if ((action = menus_get_action ("ObjectsSendtoback")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);
  if ((action = menus_get_action ("ObjectsBringtofront")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);
  if ((action = menus_get_action ("ObjectsSendbackwards")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);
  if ((action = menus_get_action ("ObjectsBringforwards")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);

  if ((action = menus_get_action ("ObjectsLayerAbove")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);
  if ((action = menus_get_action ("ObjectsLayerBelow")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 0);

  if ((action = menus_get_action ("ObjectsGroup")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsUngroup")) != NULL)
591
    gtk_action_set_sensitive (action, !textedit_active && dia && diagram_selected_any_groups (dia));
592
  if ((action = menus_get_action ("ObjectsParent")) != NULL)
593
    gtk_action_set_sensitive (action, !textedit_active && dia && diagram_selected_can_parent (dia));
594
  if ((action = menus_get_action ("ObjectsUnparent")) != NULL)
595
    gtk_action_set_sensitive (action, !textedit_active && dia && diagram_selected_any_children (dia));
596
  if ((action = menus_get_action ("ObjectsUnparentchildren")) != NULL)
597
    gtk_action_set_sensitive (action, !textedit_active && dia && diagram_selected_any_parents (dia));
598 599 600 601

  if ((action = menus_get_action ("ObjectsProperties")) != NULL)
    gtk_action_set_sensitive (action, selected_count > 0);

Lars Clausen's avatar
Lars Clausen committed
602
  /* Objects->Align menu */
603 604 605 606 607 608 609 610 611 612 613 614 615 616 617 618 619 620 621 622
  if ((action = menus_get_action ("ObjectsAlignLeft")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignCenter")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignRight")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignSpreadouthorizontally")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignAdjacent")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignTop")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignMiddle")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignBottom")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignSpreadoutvertically")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
  if ((action = menus_get_action ("ObjectsAlignStacked")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
623 624
  if ((action = menus_get_action ("ObjectsAlignConnected")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active && selected_count > 1);
625 626

  /* Select menu */
627 628 629 630 631 632 633 634 635 636 637 638 639 640 641 642 643 644 645 646 647 648 649 650
  if ((action = menus_get_action ("SelectAll")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectNone")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectInvert")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectTransitive")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectConnected")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectSametype")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);

  if ((action = menus_get_action ("SelectReplace")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectUnion")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectIntersection")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectRemove")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);
  if ((action = menus_get_action ("SelectInverse")) != NULL)
    gtk_action_set_sensitive (action, !textedit_active);

651 652 653
  /* Tools menu - toolbox actions */
  gtk_action_group_set_sensitive (menus_get_tool_actions (),  !textedit_active);

654
  /* View menu - should not need disabling yet */
655
}
Zander's avatar
Zander committed
656 657


Alexander Larsson's avatar
Alexander Larsson committed
658 659 660 661 662 663 664 665 666 667 668
void
diagram_add_ddisplay(Diagram *dia, DDisplay *ddisp)
{
  dia->displays = g_slist_prepend(dia->displays, ddisp);
}

void
diagram_remove_ddisplay(Diagram *dia, DDisplay *ddisp)
{
  dia->displays = g_slist_remove(dia->displays, ddisp);

Hans Breuer's avatar
Hans Breuer committed
669 670
  if (g_slist_length(dia->displays) == 0)
    diagram_destroy(dia);
Alexander Larsson's avatar
Alexander Larsson committed
671 672 673
}

void
Zander's avatar
Zander committed
674
diagram_add_object (Diagram *dia, DiaObject *obj)
Alexander Larsson's avatar
Alexander Larsson committed
675
{
Zander's avatar
Zander committed
676
  layer_add_object (dia->data->active_layer, obj);
Alexander Larsson's avatar
Alexander Larsson committed
677

Zander's avatar
Zander committed
678
  diagram_modified (dia);
Alexander Larsson's avatar
Alexander Larsson committed
679 680 681
}

void
Zander's avatar
Zander committed
682
diagram_add_object_list (Diagram *dia, GList *list)
Alexander Larsson's avatar
Alexander Larsson committed
683
{
Zander's avatar
Zander committed
684
  layer_add_objects (dia->data->active_layer, list);
Alexander Larsson's avatar
Alexander Larsson committed
685

Zander's avatar
Zander committed
686
  diagram_modified (dia);
Alexander Larsson's avatar
Alexander Larsson committed
687 688
}

689
void
Zander's avatar
Zander committed
690
diagram_selected_break_external (Diagram *dia)
691 692 693
{
  GList *list;
  GList *connected_list;
694 695
  DiaObject *obj;
  DiaObject *other_obj;
Zander's avatar
Zander committed
696
  int i, j;
697 698 699

  list = dia->data->selected;
  while (list != NULL) {
Zander's avatar
Zander committed
700 701
    obj = (DiaObject *) list->data;

702
    /* Break connections between this object and objects not selected: */
Zander's avatar
Zander committed
703
    for (i = 0; i < obj->num_handles; i++) {
704 705
      ConnectionPoint *con_point;
      con_point = obj->handles[i]->connected_to;
Zander's avatar
Zander committed
706 707

      if (con_point == NULL)
708
        continue; /* Not connected */
Zander's avatar
Zander committed
709

710
      other_obj = con_point->object;
Zander's avatar
Zander committed
711 712 713 714 715
      if (g_list_find (dia->data->selected, other_obj) == NULL) {
        /* other_obj is not selected, break connection */
        Change *change = undo_unconnect (dia, obj, obj->handles[i]);
        (change->apply) (change, dia);
        object_add_updates (obj, dia);
716 717
      }
    }
Zander's avatar
Zander committed
718

719
    /* Break connections from non selected objects to this object: */
Zander's avatar
Zander committed
720
    for (i = 0; i < obj->num_connections; i++) {
721
      connected_list = obj->connections[i]->connected;
Zander's avatar
Zander committed
722

723
      while (connected_list != NULL) {
Zander's avatar
Zander committed
724 725 726 727 728 729 730 731 732 733 734 735 736 737 738 739 740 741 742 743 744
        other_obj = (DiaObject *) connected_list->data;

        if (g_list_find (dia->data->selected, other_obj) == NULL) {
          /* other_obj is not in list, break all connections
             to obj from other_obj */

          for (j = 0; j < other_obj->num_handles; j++) {
            ConnectionPoint *con_point;
            con_point = other_obj->handles[j]->connected_to;

            if (con_point && (con_point->object == obj)) {
              Change *change;
              connected_list = g_list_previous (connected_list);
              change = undo_unconnect (dia, other_obj, other_obj->handles[j]);
              (change->apply) (change, dia);
              if (connected_list == NULL)
                connected_list = obj->connections[i]->connected;
            }
          }
        }
        connected_list = g_list_next (connected_list);
745 746
      }
    }
747

Zander's avatar
Zander committed
748
    list = g_list_next (list);
749 750 751
  }
}

Alexander Larsson's avatar
Alexander Larsson committed
752
void
Zander's avatar
Zander committed
753
diagram_remove_all_selected (Diagram *diagram, int delete_empty)
Alexander Larsson's avatar
Alexander Larsson committed
754
{
Zander's avatar
Zander committed
755 756 757
  object_add_updates_list (diagram->data->selected, diagram);
  textedit_remove_focus_all (diagram);
  data_remove_all_selected (diagram->data);
Alexander Larsson's avatar
Alexander Larsson committed
758 759 760
}

void
Zander's avatar
Zander committed
761
diagram_unselect_object (Diagram *diagram, DiaObject *obj)
Alexander Larsson's avatar
Alexander Larsson committed
762
{
Zander's avatar
Zander committed
763 764 765
  object_add_updates (obj, diagram);
  textedit_remove_focus (obj, diagram);
  data_unselect (DIA_DIAGRAM_DATA (diagram), obj);
Alexander Larsson's avatar
Alexander Larsson committed
766 767 768
}

void
769
diagram_unselect_objects(Diagram *dia, GList *obj_list)
Alexander Larsson's avatar
Alexander Larsson committed
770
{
771
  GList *list;
772
  DiaObject *obj;
773

774
  /* otherwise we would signal objects step by step */
775
  g_signal_handlers_block_by_func (dia, DIA_DIAGRAM_DATA_GET_CLASS (dia)->selection_changed, NULL);
776 777
  list = obj_list;
  while (list != NULL) {
778
    obj = (DiaObject *) list->data;
779 780

    if (g_list_find(dia->data->selected, obj) != NULL){
781
      diagram_unselect_object(dia, obj);
782 783 784 785
    }

    list = g_list_next(list);
  }
786
  g_signal_handlers_unblock_by_func (dia, DIA_DIAGRAM_DATA_GET_CLASS (dia)->selection_changed, NULL);
787
  g_signal_emit_by_name (dia, "selection_changed", g_list_length (dia->data->selected));
788 789
}

790 791 792 793 794 795
/** Make a single object selected.
 * Note that an object inside a closed group cannot be made selected, nor
 * can an object in a non-active layer.
 * @param diagram The diagram that the object belongs to (sorta redundant now)
 * @param obj The object that should be made selected.
 */
796
void
797
diagram_select(Diagram *diagram, DiaObject *obj)
798
{
799 800 801 802 803
  if (dia_object_is_selectable(obj)) {
    data_select(diagram->data, obj);
    obj->ops->selectf(obj, NULL, NULL);
    object_add_updates(obj, diagram);
  }
Alexander Larsson's avatar
Alexander Larsson committed
804 805 806
}

void
807
diagram_select_list(Diagram *dia, GList *list)
Alexander Larsson's avatar
Alexander Larsson committed
808
{
809
  g_return_if_fail (dia && list);
810
  /* otherwise we would signal objects step by step */
811
  g_signal_handlers_block_by_func (dia, DIA_DIAGRAM_DATA_GET_CLASS (dia)->selection_changed, NULL);
Alexander Larsson's avatar
Alexander Larsson committed
812
  while (list != NULL) {
813
    DiaObject *obj = (DiaObject *)list->data;
Alexander Larsson's avatar
Alexander Larsson committed
814

815
    diagram_select(dia, obj);
Alexander Larsson's avatar
Alexander Larsson committed
816 817 818

    list = g_list_next(list);
  }
819
  if (get_active_focus((DiagramData*) dia) == NULL) {
820 821
    textedit_activate_first(ddisplay_active());
  }
822
  g_signal_handlers_unblock_by_func (dia, DIA_DIAGRAM_DATA_GET_CLASS (dia)->selection_changed, NULL);
823
  g_signal_emit_by_name (dia, "selection_changed", g_list_length (dia->data->selected));
Alexander Larsson's avatar
Alexander Larsson committed
824 825 826
}

int
827
diagram_is_selected(Diagram *diagram, DiaObject *obj)
Alexander Larsson's avatar
Alexander Larsson committed
828
{
829
  return g_list_find(diagram->data->selected, obj) != NULL;
Alexander Larsson's avatar
Alexander Larsson committed
830 831
}

832 833 834 835 836 837 838 839 840 841 842 843 844 845 846 847
void
diagram_redraw_all()
{
  GList *list;
  Diagram *dia;

  list = open_diagrams;

  while (list != NULL) {
    dia = (Diagram *) list->data;

    diagram_add_update_all(dia);
    diagram_flush(dia);

    list = g_list_next(list);
  }
848
  return;
849 850
}

851 852 853 854 855
void
diagram_add_update_all(Diagram *dia)
{
  GSList *l;
  DDisplay *ddisp;
Zander's avatar
Zander committed
856

857
  l = dia->displays;
Lars Clausen's avatar
Lars Clausen committed
858
  while (l!=NULL) {
859 860 861
    ddisp = (DDisplay *) l->data;

    ddisplay_add_update_all(ddisp);
Zander's avatar
Zander committed
862

863 864 865
    l = g_slist_next(l);
  }
}
866

Alexander Larsson's avatar
Alexander Larsson committed
867
void
868
diagram_add_update(Diagram *dia, const Rectangle *update)
Alexander Larsson's avatar
Alexander Larsson committed
869 870 871
{
  GSList *l;
  DDisplay *ddisp;
Zander's avatar
Zander committed
872

Alexander Larsson's avatar
Alexander Larsson committed
873
  l = dia->displays;
Lars Clausen's avatar
Lars Clausen committed
874
  while (l!=NULL) {
Alexander Larsson's avatar
Alexander Larsson committed
875 876 877
    ddisp = (DDisplay *) l->data;

    ddisplay_add_update(ddisp, update);
Zander's avatar
Zander committed
878

Alexander Larsson's avatar
Alexander Larsson committed
879 880 881 882
    l = g_slist_next(l);
  }
}

883 884 885 886 887 888
/** Add an update of the given rectangle, but with an additional
 * border around it.  The pixels are added after the rectangle has
 * been converted to pixel coords.
 * Currently used for leaving room for highlighting.
 * */
void
889
diagram_add_update_with_border(Diagram *dia, const Rectangle *update,
890 891 892 893
			       int pixel_border)
{
  GSList *l;
  DDisplay *ddisp;
Zander's avatar
Zander committed
894

895 896 897 898 899
  l = dia->displays;
  while (l!=NULL) {
    ddisp = (DDisplay *) l->data;

    ddisplay_add_update_with_border(ddisp, update, pixel_border);
Zander's avatar
Zander committed
900

901 902 903 904
    l = g_slist_next(l);
  }
}

Alexander Larsson's avatar
Alexander Larsson committed
905 906 907 908 909 910 911 912
void
diagram_add_update_pixels(Diagram *dia, Point *point,
			  int pixel_width, int pixel_height)
{
  GSList *l;
  DDisplay *ddisp;

  l = dia->displays;
Lars Clausen's avatar
Lars Clausen committed
913
  while (l!=NULL) {
Alexander Larsson's avatar
Alexander Larsson committed
914 915 916
    ddisp = (DDisplay *) l->data;

    ddisplay_add_update_pixels(ddisp, point, pixel_width, pixel_height);
Zander's avatar
Zander committed
917

Alexander Larsson's avatar
Alexander Larsson committed
918 919 920 921 922 923 924 925 926 927
    l = g_slist_next(l);
  }
}

void
diagram_flush(Diagram *dia)
{
  GSList *l;
  DDisplay *ddisp;
  l = dia->displays;
Lars Clausen's avatar
Lars Clausen committed
928
  while (l!=NULL) {
Alexander Larsson's avatar
Alexander Larsson committed
929 930 931
    ddisp = (DDisplay *) l->data;

    ddisplay_flush(ddisp);
Zander's avatar
Zander committed
932

Alexander Larsson's avatar
Alexander Larsson committed
933 934
    l = g_slist_next(l);
  }
935
  dynobj_refresh_kick();
Alexander Larsson's avatar
Alexander Larsson committed
936 937
}

938
DiaObject *
Alexander Larsson's avatar
Alexander Larsson committed
939 940 941
diagram_find_clicked_object(Diagram *dia, Point *pos,
			    real maxdist)
{
Zander's avatar
Zander committed
942
  return layer_find_closest_object_except(dia->data->active_layer,
Lars Clausen's avatar
Lars Clausen committed
943 944 945
					  pos, maxdist, NULL);
}

946
DiaObject *
Lars Clausen's avatar
Lars Clausen committed
947 948 949 950 951
diagram_find_clicked_object_except(Diagram *dia, Point *pos,
				   real maxdist, GList *avoid)
{
  return layer_find_closest_object_except(dia->data->active_layer, pos,
					  maxdist, avoid);
Alexander Larsson's avatar
Alexander Larsson committed
952 953
}

954 955 956 957
/*
 * Always returns the last handle in an object that has
 * the closest distance
 */
Alexander Larsson's avatar
Alexander Larsson committed
958 959
real
diagram_find_closest_handle(Diagram *dia, Handle **closest,
960
			    DiaObject **object, Point *pos)
Alexander Larsson's avatar
Alexander Larsson committed
961 962
{
  GList *l;
963
  DiaObject *obj;
Alexander Larsson's avatar
Alexander Larsson committed
964 965 966 967 968
  Handle *handle;
  real mindist, dist;
  int i;

  mindist = 1000000.0; /* Realy big value... */
Zander's avatar
Zander committed
969

Alexander Larsson's avatar
Alexander Larsson committed
970
  *closest = NULL;
Zander's avatar
Zander committed
971

972
  l = dia->data->selected;
Lars Clausen's avatar
Lars Clausen committed
973
  while (l!=NULL) {
974
    obj = (DiaObject *) l->data;
Alexander Larsson's avatar
Alexander Larsson committed
975 976 977 978 979

    for (i=0;i<obj->num_handles;i++) {
      handle = obj->handles[i];
      /* Note: Uses manhattan metric for speed... */
      dist = distance_point_point_manhattan(pos, &handle->pos);
Zander's avatar
Zander committed
980
      if (dist<=mindist) {
Alexander Larsson's avatar
Alexander Larsson committed
981 982 983 984 985
	mindist = dist;
	*closest = handle;
	*object = obj;
      }
    }
Zander's avatar
Zander committed
986

Alexander Larsson's avatar
Alexander Larsson committed
987 988 989 990 991 992 993 994 995
    l = g_list_next(l);
  }

  return mindist;
}

real
diagram_find_closest_connectionpoint(Diagram *dia,
				     ConnectionPoint **closest,
Lars Clausen's avatar
Lars Clausen committed
996
				     Point *pos,
997
				     DiaObject *notthis)
Alexander Larsson's avatar
Alexander Larsson committed
998
{
Lars Clausen's avatar
Lars Clausen committed
999
  real dist = 100000000.0;
1000
  guint i;
Lars Clausen's avatar
Lars Clausen committed
1001 1002 1003 1004 1005 1006 1007 1008 1009 1010 1011 1012 1013 1014
  for (i=0;i<dia->data->layers->len;i++) {
    Layer *layer = (Layer*)g_ptr_array_index(dia->data->layers, i);
    ConnectionPoint *this_cp;
    real this_dist;
    if (layer->connectable) {
      this_dist = layer_find_closest_connectionpoint(layer,
						     &this_cp, pos, notthis);
      if (this_dist < dist) {
	dist = this_dist;
	*closest = this_cp;
      }
    }
  }
  return dist;
Alexander Larsson's avatar
Alexander Larsson committed
1015 1016 1017 1018 1019
}

void
diagram_update_extents(Diagram *dia)
{
1020
  gfloat cur_scale = dia->data->paper.scaling;
Alexander Larsson's avatar
Alexander Larsson committed
1021 1022
  /* anropar update_scrollbars() */

1023 1024 1025 1026
  if (data_update_extents(dia->data)) {
    /* Update scrollbars because extents were changed: */
    GSList *l;
    DDisplay *ddisp;
Zander's avatar
Zander committed
1027

1028
    l = dia->displays;
Lars Clausen's avatar
Lars Clausen committed
1029
    while (l!=NULL) {
1030
      ddisp = (DDisplay *) l->data;
Zander's avatar
Zander committed
1031

1032
      ddisplay_update_scrollbars(ddisp);
Zander's avatar
Zander committed
1033

1034
      l = g_slist_next(l);
Alexander Larsson's avatar
Alexander Larsson committed
1035
    }
1036 1037 1038 1039
    if (cur_scale != dia->data->paper.scaling) {
      diagram_add_update_all(dia);
      diagram_flush(dia);
    }
Zander's avatar
Zander committed
1040
  }
Alexander Larsson's avatar
Alexander Larsson committed
1041 1042 1043 1044
}

/* Remove connections from obj to objects outside created group. */
static void
1045
strip_connections(DiaObject *obj, GList *not_strip_list, Diagram *dia)
Alexander Larsson's avatar
Alexander Larsson committed
1046 1047 1048
{
  int i;
  Handle *handle;
1049
  Change *change;
Alexander Larsson's avatar
Alexander Larsson committed
1050 1051 1052 1053 1054

  for (i=0;i<obj->num_handles;i++) {
    handle = obj->handles[i];
    if ((handle->connected_to != NULL) &&
	(g_list_find(not_strip_list, handle->connected_to->object)==NULL)) {
1055 1056
      change = undo_unconnect(dia, obj, handle);
      (change->apply)(change, dia);
Alexander Larsson's avatar
Alexander Larsson committed
1057 1058 1059 1060
    }
  }
}

1061

1062
/* GCompareFunc */
Zander's avatar
Zander committed
1063
static gint
1064
diagram_parent_sort_cb(gconstpointer _a, gconstpointer _b)
Lars Clausen's avatar
Lars Clausen committed
1065
{
1066 1067
  ObjectExtent **a = (ObjectExtent **)_a;
  ObjectExtent **b = (ObjectExtent **)_b;
1068

1069
  if ((*a)->extent.left < (*b)->extent.left)
Lars Clausen's avatar
Lars Clausen committed
1070
    return 1;
1071
  else if ((*a)->extent.left > (*b)->extent.left)
Lars Clausen's avatar
Lars Clausen committed
1072 1073
    return -1;
  else
1074
    if ((*a)->extent.top < (*b)->extent.top)
Lars Clausen's avatar
Lars Clausen committed
1075
      return 1;
1076
    else if ((*a)->extent.top > (*b)->extent.top)
Lars Clausen's avatar
Lars Clausen committed
1077 1078 1079 1080 1081 1082
      return -1;
    else
      return 0;
}


1083 1084
/* needs faster algorithm -- if we find that parenting is slow.
 * If it works, don't optimize it until it's a hotspot. */
Lars Clausen's avatar
Lars Clausen committed
1085 1086 1087 1088 1089
void diagram_parent_selected(Diagram *dia)
{
  GList *list = dia->data->selected;
  int length = g_list_length(list);
  int idx, idx2;
1090
  ObjectExtent *oe;
1091
  gboolean any_parented = FALSE;
1092
  GPtrArray *extents = g_ptr_array_sized_new(length);
Lars Clausen's avatar
Lars Clausen committed
1093 1094
  while (list)
  {
1095
    oe = g_new(ObjectExtent, 1);
Lars Clausen's avatar
Lars Clausen committed
1096
    oe->object = list->data;
1097 1098
    parent_handle_extents(list->data, &oe->extent);
    g_ptr_array_add(extents, oe);
Lars Clausen's avatar
Lars Clausen committed
1099 1100
    list = g_list_next(list);
  }
1101 1102
  /* sort all the objects by their left position */
  g_ptr_array_sort(extents, diagram_parent_sort_cb);
Lars Clausen's avatar
Lars Clausen committed
1103 1104 1105

  for (idx = 0; idx < length; idx++)
  {
1106 1107
    ObjectExtent *oe1 = g_ptr_array_index(extents, idx);
    if (oe1->object->parent)
Lars Clausen's avatar
Lars Clausen committed
1108 1109 1110 1111
      continue;

    for (idx2 = idx + 1; idx2 < length; idx2++)
    {
1112
      ObjectExtent *oe2 = g_ptr_array_index(extents, idx2);
1113
      if (!object_flags_set(oe2->object, DIA_OBJECT_CAN_PARENT))
Lars Clausen's avatar
Lars Clausen committed
1114 1115
        continue;

1116 1117
      if (oe1->extent.right <= oe2->extent.right
        && oe1->extent.bottom <= oe2->extent.bottom)
Lars Clausen's avatar
Lars Clausen committed
1118
      {
1119
	Change *change;
1120
	change = undo_parenting(dia, oe2->object, oe1->object, TRUE);
1121 1122 1123
	(change->apply)(change, dia);
	any_parented = TRUE;
	/*
1124 1125
        oe1->object->parent = oe2->object;
	oe2->object->children = g_list_append(oe2->object->children, oe1->object);
1126
	*/
Lars Clausen's avatar
Lars Clausen committed
1127 1128 1129 1130
	break;
      }
    }
  }
1131
  g_ptr_array_free(extents, TRUE);
1132 1133 1134 1135 1136
  if (any_parented) {
    diagram_modified(dia);
    diagram_flush(dia);
    undo_set_transactionpoint(dia->undo);
  }
Lars Clausen's avatar
Lars Clausen committed
1137 1138
}

Lars Clausen's avatar
Lars Clausen committed
1139
/** Remove all selected objects from their parents (if any). */
Lars Clausen's avatar
Lars Clausen committed
1140 1141
void diagram_unparent_selected(Diagram *dia)
{
Lars Clausen's avatar
Lars Clausen committed
1142
  GList *list;
1143
  DiaObject *obj, *parent;
1144 1145 1146
  Change *change;
  gboolean any_unparented = FALSE;

Lars Clausen's avatar
Lars Clausen committed
1147 1148
  for (list = dia->data->selected; list != NULL; list = g_list_next(list))
  {
1149
    obj = (DiaObject *) list->data;
Lars Clausen's avatar
Lars Clausen committed
1150
    parent = obj->parent;
Zander's avatar
Zander committed
1151

Lars Clausen's avatar
Lars Clausen committed
1152 1153 1154
    if (!parent)
      continue;

1155 1156 1157 1158
    change = undo_parenting(dia, parent, obj, FALSE);
    (change->apply)(change, dia);
    any_unparented = TRUE;
    /*
Lars Clausen's avatar
Lars Clausen committed
1159 1160
    parent->children = g_list_remove(parent->children, obj);
    obj->parent = NULL;
1161 1162 1163 1164 1165 1166
    */
  }
  if (any_unparented) {
    diagram_modified(dia);
    diagram_flush(dia);
    undo_set_transactionpoint(dia->undo);
Lars Clausen's avatar
Lars Clausen committed
1167 1168 1169 1170 1171 1172 1173
  }
}

/** Remove all children from the selected parents. */
void diagram_unparent_children_selected(Diagram *dia)
{
  GList *list;
1174
  DiaObject *obj, *child;
1175
  gboolean any_unparented = FALSE;
Lars Clausen's avatar
Lars Clausen committed
1176
  for (list = dia->data->selected; list != NULL; list = g_list_next(list))
Lars Clausen's avatar
Lars Clausen committed
1177
  {
1178
    obj = (DiaObject *) list->data;
1179
    if (!object_flags_set(obj, DIA_OBJECT_CAN_PARENT) || !obj->children)
Lars Clausen's avatar
Lars Clausen committed
1180 1181
      continue;

1182 1183 1184 1185 1186 1187 1188
    any_unparented = TRUE;
    /* Yes, this creates a whole bunch of Changes.  They're lightweight
     * structures, though, and it's easier to assure correctness this
     * way.  If needed, we can make a parent undo with a list of children.
     */
    while (obj->children != NULL) {
      Change *change;
1189
      child = (DiaObject *) obj->children->data;
1190 1191 1192 1193 1194 1195 1196 1197 1198 1199 1200 1201
      change = undo_parenting(dia, obj, child, FALSE);
      /* This will remove one item from the list, so the while terminates. */
      (change->apply)(change, dia);
    }
    if (obj->children != NULL)
      printf("Obj still has %d children\n",
	     g_list_length(obj->children));
  }
  if (any_unparented) {
    diagram_modified(dia);
    diagram_flush(dia);
    undo_set_transactionpoint(dia->undo);
Lars Clausen's avatar
Lars Clausen committed
1202 1203 1204
  }
}

Alexander Larsson's avatar
Alexander Larsson committed
1205 1206 1207 1208
void diagram_group_selected(Diagram *dia)
{
  GList *list;
  GList *group_list;
1209 1210
  DiaObject *group;
  DiaObject *obj;
1211
  GList *orig_list;
1212
  Change *change;
1213

1214
  if (g_list_length(dia->data->selected) < 1) {
Lars Clausen's avatar
Lars Clausen committed
1215
    message_error(_("Trying to group with no selected objects."));
1216 1217
    return;
  }
Zander's avatar
Zander committed
1218

1219 1220 1221
#if 0
  /* the following is wrong as it screws up the selected list, see bug #153525
     * I just don't get what was originally intented so please speak up if you know  --hb
Zander's avatar
Zander committed
1222
     */
Lars Clausen's avatar
Lars Clausen committed
1223
  dia->data->selected = parent_list_affected(dia->data->selected);
1224
#endif
Zander's avatar
Zander committed
1225

1226
  orig_list = g_list_copy(dia->data->active_layer->objects);
Zander's avatar
Zander committed
1227

Alexander Larsson's avatar
Alexander Larsson committed
1228 1229 1230
  /* We have to rebuild the selection list so that it is the same
     order as in the Diagram list. */
  group_list = diagram_get_sorted_selected_remove(dia);
Zander's avatar
Zander committed
1231

Alexander Larsson's avatar
Alexander Larsson committed
1232 1233
  list = group_list;
  while (list != NULL) {
1234
    obj = (DiaObject *)list->data;
1235 1236

    /* Remove connections from obj to objects outside created group. */
1237 1238
    /* strip_connections sets up its own undo info. */
    /* The connections aren't reattached by ungroup. */
1239
    strip_connections(obj, dia->data->selected, dia);
Zander's avatar
Zander committed
1240

Alexander Larsson's avatar
Alexander Larsson committed
1241 1242 1243
    list = g_list_next(list);
  }

1244
  /* Remove list of selected objects */
Lars Clausen's avatar
Lars Clausen committed
1245
  textedit_remove_focus_all(dia);