window_relationships_overview.cc 16.7 KB
Newer Older
Murray Cumming's avatar
Murray Cumming committed
1
/* Glom
2
 *
3
 * Copyright (C) 2001-2013 Murray Cumming
Murray Cumming's avatar
Murray Cumming committed
4 5 6 7 8 9 10 11 12 13 14 15 16
 *
 * 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, write to the
17 18
 * Free Software Foundation, Inc., 51 Franklin Street, Fifth Floor,
 * Boston, MA 02110-1301 USA.
Murray Cumming's avatar
Murray Cumming committed
19 20
 */

21
#include "config.h"
22
#include "window_relationships_overview.h"
23 24
#include "glom/utility_widgets/canvas/canvas_line_movable.h"
#include "glom/utility_widgets/canvas/canvas_text_movable.h"
25
#include <glom/mode_design/layout/dialog_choose_relationship.h>
26
#include "printoperation_relationshipsoverview.h"
27
#include "glom/appwindow.h"
28
#include <goocanvas.h>
29 30
#include <giomm/menu.h>
#include <giomm/simpleactiongroup.h>
31
#include <glibmm/i18n.h>
David King's avatar
David King committed
32
#include <iostream>
Murray Cumming's avatar
Murray Cumming committed
33

34 35
namespace Glom
{
Murray Cumming's avatar
Murray Cumming committed
36

Murray Cumming's avatar
Murray Cumming committed
37
//static:
38 39
int Window_RelationshipsOverview::m_last_size_x = 0;
int Window_RelationshipsOverview::m_last_size_y = 0;
Murray Cumming's avatar
Murray Cumming committed
40

41 42
const char* Window_RelationshipsOverview::glade_id("window_relationships_overview");
const bool Window_RelationshipsOverview::glade_developer(true);
Murray Cumming's avatar
Murray Cumming committed
43

44
Window_RelationshipsOverview::Window_RelationshipsOverview(BaseObjectType* cobject, const Glib::RefPtr<Gtk::Builder>& builder)
45
  : Gtk::ApplicationWindow(cobject),
46
    m_builder(builder),
47
    m_modified(false),
Murray Cumming's avatar
Murray Cumming committed
48
    m_scrolledwindow_canvas(nullptr)
Murray Cumming's avatar
Murray Cumming committed
49
{
Murray Cumming's avatar
Murray Cumming committed
50
  Gtk::Button* button_close = nullptr;
51 52 53 54
  builder->get_widget("button_close",  button_close);
  if(button_close)
    button_close->signal_clicked().connect( sigc::mem_fun(*this, &Window_RelationshipsOverview::on_button_close) );

55 56
  m_page_setup = Gtk::PageSetup::create();
  m_settings = Gtk::PrintSettings::create();
57

58
  //Add a menu:
Murray Cumming's avatar
Murray Cumming committed
59
  Gtk::Box* vbox = nullptr;
60
  builder->get_widget("vbox_placeholder_menubar", vbox);
61

62
  auto action_group = Gio::SimpleActionGroup::create();
63

64
  add_action("pagesetup",
65
    sigc::mem_fun(*this, &Window_RelationshipsOverview::on_menu_file_page_setup) );
66
  add_action("print",
67
    sigc::mem_fun(*this, &Window_RelationshipsOverview::on_menu_file_print) );
68

69 70 71
  m_action_showgrid = Gio::SimpleAction::create_bool("showgrid", false);
  action_group->add_action(m_action_showgrid);
  m_action_showgrid->signal_activate().connect(
72
    sigc::mem_fun(*this, &Window_RelationshipsOverview::on_menu_view_showgrid)
73
  );
74

75
  insert_action_group("relationshipsoverview", action_group);
76 77

  //Get the menu:
78
  auto object =
79
    builder->get_object("Overview_MainMenu");
80
  auto gmenu =
81 82 83 84
    Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
  if(!gmenu)
    g_warning("GMenu not found");

85 86 87
  auto menu = std::make_unique<Gtk::MenuBar>(gmenu);
  menu->show();
  vbox->pack_start(*(Gtk::manage(menu.release())), Gtk::PACK_SHRINK);
88 89 90


  //Get the scolled window and add the canvas to it:
Murray Cumming's avatar
Murray Cumming committed
91
  m_scrolledwindow_canvas = nullptr;
92
  builder->get_widget("scrolledwindow_canvas", m_scrolledwindow_canvas);
93

94
  m_scrolledwindow_canvas->add(m_canvas);
95
  m_canvas.show();
96

97
  //Restore the previous window size, to avoid annoying the user:
Murray Cumming's avatar
Murray Cumming committed
98
  if(m_last_size_x != 0 && m_last_size_y != 0 )
99
  {
100
    set_default_size(m_last_size_x, m_last_size_y);
101
  }
102 103 104 105 106 107 108

  m_group_tables = Goocanvas::Group::create();
  m_canvas.add_item(m_group_tables);
  m_group_lines = Goocanvas::Group::create();
  m_canvas.add_item(m_group_lines);
  m_group_lines->lower(); //Make sure that the lines are below the tables.

109 110
  //Respond to changes of window size,
  //so we always make the canvas bounds big enough:
111
  m_scrolledwindow_canvas->get_hadjustment()->signal_changed().connect(
112
    sigc::mem_fun(*this, &Window_RelationshipsOverview::on_scroll_value_changed) );
113
  m_scrolledwindow_canvas->get_vadjustment()->signal_changed().connect(
114
    sigc::mem_fun(*this, &Window_RelationshipsOverview::on_scroll_value_changed) );
115

116
  setup_context_menu();
Murray Cumming's avatar
Murray Cumming committed
117
}
Murray Cumming's avatar
Murray Cumming committed
118

119
Window_RelationshipsOverview::~Window_RelationshipsOverview()
Murray Cumming's avatar
Murray Cumming committed
120 121
{
  get_size(m_last_size_x, m_last_size_y);
122

123 124 125
  //Remove all current items:
  //while(m_group_tables->get_n_children() > 0)
  //  m_group_tables->remove_child(0);
Murray Cumming's avatar
Murray Cumming committed
126 127 128
}


129
void Window_RelationshipsOverview::draw_tables()
Murray Cumming's avatar
Murray Cumming committed
130
{
131 132 133
  //Remove all current items:
  while(m_group_tables->get_n_children() > 0)
    m_group_tables->remove_child(0);
134

135
  const auto document = std::dynamic_pointer_cast<Document>(get_document());
Murray Cumming's avatar
Murray Cumming committed
136
  if(document)
137
  {
138 139 140
    double max_table_height = 0;
    double sizex = 10;
    double sizey = 10;
Murray Cumming's avatar
Murray Cumming committed
141

142
    //Create tables canvas items, with lists of fields:
143
    const auto tables = document->get_tables();
Murray Cumming's avatar
Murray Cumming committed
144
    for(const auto& info : tables)
145
    {
Murray Cumming's avatar
Murray Cumming committed
146
      const auto table_name = info->get_name();
147

148 149 150 151
      float table_x = 0;
      float table_y = 0;
      //Get the x and y position from the document:
      if(!document->get_table_overview_position(table_name, table_x, table_y))
152
      {
153 154 155
        table_x = sizex;
        table_y = sizey;
        document->set_table_overview_position(table_name, table_x, table_y);
Murray Cumming's avatar
Murray Cumming committed
156 157
        m_modified = true;
      }
158

159
      Document::type_vec_fields fields = document->get_table_fields(table_name);
160

161
      auto table_group =
162
        CanvasGroupDbTable::create(info->get_name(), item_get_title_or_name(info), fields, table_x, table_y);
163
      m_group_tables->add_child(table_group);
164
      m_canvas.associate_with_grid(table_group); //Make snapping work.
165

166
      table_group->signal_moved().connect(
167
        sigc::mem_fun(*this, &Window_RelationshipsOverview::on_table_moved));
168

169
      table_group->signal_show_context().connect( sigc::bind(
170
        sigc::mem_fun(*this, &Window_RelationshipsOverview::on_table_show_context),
171
        table_group) );
172

173 174
      //tv->x2 = tv->x1 + table_width;
      //tv->y2 = tv->y1 + table_height;
175

176
      sizex += table_group->get_table_width() + 10;
177

178
      max_table_height = std::max(max_table_height, table_group->get_table_height());
Murray Cumming's avatar
Murray Cumming committed
179
    }
180 181 182 183

    m_canvas.set_bounds(0, 0, sizex, max_table_height * tables.size());
  }
}
184

185
void Window_RelationshipsOverview::draw_lines()
186 187 188 189
{
  //Remove all current items:
  while(m_group_lines->get_n_children() > 0)
    m_group_lines->remove_child(0);
190

191
  const auto document = std::dynamic_pointer_cast<Document>(get_document());
192
  if(document)
193
  {
194
    //Create the lines linking tables to show relationships:
195
    for(const auto& info : document->get_tables())
Murray Cumming's avatar
Murray Cumming committed
196
    {
Murray Cumming's avatar
Murray Cumming committed
197
      const auto table_name = info->get_name();
198

199 200
      Document::type_vec_relationships m_relationships = document->get_relationships(table_name);
      Document::type_vec_fields fields = document->get_table_fields(table_name);
201

202
      for(const auto& relationship : m_relationships)
203
      {
204 205 206
        if(!relationship)
          continue;

207
        auto group_from = get_table_group(relationship->get_from_table());
208 209 210 211 212 213 214 215 216

        double from_field_x = 0.0;
        double from_field_y = 0.0;

        if(group_from)
        {
          double temp_x = 0.0;
          double temp_y = 0.0;
          group_from->get_xy(temp_x, temp_y);
217

218 219 220 221 222 223
          from_field_x = temp_x;
          from_field_y = temp_y + group_from->get_field_y(relationship->get_from_field());
        }

        //Only primary keys can be to fields:
        if(true) //document->get_field(relationship->get_to_table(), relationship->get_to_field())->get_primary_key())
224
        {
225
          auto group_to = get_table_group(relationship->get_to_table());
226 227 228 229 230 231 232 233 234 235 236 237 238 239

          double to_field_x = 0.0;
          double to_field_y = 0.0;

          if(group_to)
          {
            double temp_x = 0.0;
            double temp_y = 0.0;
            group_to->get_xy(temp_x, temp_y);
            to_field_x = temp_x;
            to_field_y = temp_y + group_to->get_field_y(relationship->get_to_field());
          }

          //Start the line from the right of the from table instead of the left, if the to table is to the right:
240
          double extra_line = 0; //An extra horizontal line before the real diagonal line starts.
241 242 243 244 245 246 247 248 249 250
          if(to_field_x > from_field_x)
          {
            from_field_x += group_from->get_table_width();
            extra_line = 20;
          }
          else
          {
            to_field_x += group_to->get_table_width();
            extra_line = -20;
          }
251

252
          //Create the line:
253
          auto line = CanvasLineMovable::create();
254 255 256 257 258 259 260 261 262 263 264 265 266 267 268 269 270
          double points_coordinates[] = {from_field_x, from_field_y,
            from_field_x + extra_line, from_field_y,
            to_field_x - extra_line, to_field_y,
            to_field_x, to_field_y};
          Goocanvas::Points points(4, points_coordinates);
          line->property_points() = points;
          line->property_stroke_color() = "black";
          line->property_line_width() = 1.0;
          line->property_start_arrow() = false;
          line->property_end_arrow() = true;
          line->property_arrow_width() = 10.0;
          line->property_arrow_length() = 10.0;
          line->set_movement_allowed(false, false); //Don't let the user move this by dragging.
          m_group_lines->add_child(line);

          //Create a text item, showing the name of the relationship on the line:
          //
271
          //Raise or lower the text slightly to make it show above the line when horizontal,
272 273
          //and to avoid overwriting a relationship in the other direction:
          //TODO: This is not very clear. Investigate how other systems show this.
274
          double y_offset = (from_field_x < to_field_x) ? -10 : +10;
275
          if(from_field_x == to_field_x)
276
            y_offset = (from_field_y < to_field_y) ? -10 : +10;
277 278

          const double text_x = (from_field_x + to_field_x) / 2;
Murray Cumming's avatar
Murray Cumming committed
279
          const auto text_y = ((from_field_y + to_field_y) / 2) + y_offset;
280
          auto text = CanvasTextMovable::create(item_get_title_or_name(relationship),
281
            text_x, text_y, -1, //TODO: Calc a suitable width.
282
            Goocanvas::ANCHOR_CENTER);
283
          text->property_font() = "Sans 10";
284 285 286
          text->property_use_markup() = true;
          text->set_movement_allowed(false, false); //Move only as part of the parent group.
          m_group_lines->add_child(text);
287
        }
288
      }
289
    }
290
  }
Murray Cumming's avatar
Murray Cumming committed
291 292
  else
  {
293
    std::cout << "ERROR: Could not retrieve the Glom document.\n";
Murray Cumming's avatar
Murray Cumming committed
294 295
  }
}
296

297
void Window_RelationshipsOverview::load_from_document()
Murray Cumming's avatar
Murray Cumming committed
298
{
299 300 301
  draw_tables();
  draw_lines();
}
Murray Cumming's avatar
Murray Cumming committed
302

303
void Window_RelationshipsOverview::on_menu_file_print()
Murray Cumming's avatar
Murray Cumming committed
304
{
305
  print_or_preview(Gtk::PRINT_OPERATION_ACTION_PRINT_DIALOG);
Murray Cumming's avatar
Murray Cumming committed
306 307
}

308
void Window_RelationshipsOverview::on_menu_file_page_setup()
Murray Cumming's avatar
Murray Cumming committed
309
{
310
  //Show the page setup dialog, asking it to start with the existing settings:
311
  auto new_page_setup =
312
      Gtk::run_page_setup_dialog(*this, m_page_setup, m_settings);
313 314 315

  //Save the chosen page setup dialog for use when printing, previewing, or
  //showing the page setup dialog again:
316
  m_page_setup = new_page_setup;
Murray Cumming's avatar
Murray Cumming committed
317 318
}

319
void Window_RelationshipsOverview::on_menu_view_showgrid(const Glib::VariantBase& /* parameter */)
Murray Cumming's avatar
Murray Cumming committed
320
{
321 322
  bool showgrid = false;
  m_action_showgrid->get_state(showgrid);
323 324 325 326 327

  //Change the state, because this doesn't happen automatically:
  showgrid = !showgrid;
  m_action_showgrid->change_state(showgrid);

328
  if(showgrid)
Murray Cumming's avatar
Murray Cumming committed
329
  {
330
    m_canvas.set_grid_gap(40);
Murray Cumming's avatar
Murray Cumming committed
331
  }
332
  else
Murray Cumming's avatar
Murray Cumming committed
333
  {
334
    m_canvas.remove_grid();
Murray Cumming's avatar
Murray Cumming committed
335
  }
336 337
}

338
//TODO: Is this used?
339
void Window_RelationshipsOverview::on_menu_file_save()
340 341 342
{
}

343
void Window_RelationshipsOverview::print_or_preview(Gtk::PrintOperationAction print_action)
344 345 346
{
  //Create a new PrintOperation with our PageSetup and PrintSettings:
  //(We use our derived PrintOperation class)
347
  auto print = PrintOperationRelationshipsOverview::create();
348 349 350
  print->set_canvas(&m_canvas);

  print->set_track_print_status();
351 352
  print->set_default_page_setup(m_page_setup);
  print->set_print_settings(m_settings);
353 354 355 356 357 358 359 360

  //print->signal_done().connect(sigc::bind(sigc::mem_fun(*this,
  //                &ExampleWindow::on_printoperation_done), print));

  try
  {
    print->run(print_action /* print or preview */, *this);
  }
361
  catch(const Gtk::PrintError& ex)
362 363
  {
    //See documentation for exact Gtk::PrintError error codes.
364
    std::cerr << G_STRFUNC << ": An error occurred while trying to run a print operation:"
365 366 367 368
        << ex.what() << std::endl;
  }
}

369
Glib::RefPtr<CanvasGroupDbTable> Window_RelationshipsOverview::get_table_group(const Glib::ustring& table_name)
370
{
Murray Cumming's avatar
Murray Cumming committed
371
  const auto count = m_group_tables->get_n_children();
372 373
  for(int i = 0; i < count; ++i)
  {
374 375
    auto item = m_group_tables->get_child(i);
    auto table_item = Glib::RefPtr<CanvasGroupDbTable>::cast_dynamic(item);
376 377 378 379 380 381 382 383 384 385
    if(table_item && (table_item->get_table_name() == table_name))
    {
      return table_item;
    }

  }

  return Glib::RefPtr<CanvasGroupDbTable>();
}

386
void Window_RelationshipsOverview::on_table_moved(const Glib::RefPtr<CanvasItemMovable>& item, double /* x_offset */, double /* y_offset */)
387
{
Murray Cumming's avatar
Murray Cumming committed
388
  auto table =
389 390 391 392
    Glib::RefPtr<CanvasGroupDbTable>::cast_dynamic(item);
  if(!table)
    return;

393
  auto document = std::dynamic_pointer_cast<Document>(get_document());
394 395 396 397 398 399 400 401 402
  if(document && table)
  {
    //Save the new position in the document:
    double x = 0;
    double y = 0;
    table->get_xy(x, y);
    document->set_table_overview_position(table->get_table_name(), x, y);
  }

403
  //It is probably incredibly inefficient to recreate the lines repeatedly while dragging a table,
404 405 406 407 408
  //but it seems to work OK, and it makes the code much simpler.
  //If this is a problem, we should just change the start/end coordinates of any lines connected to the moved table.
  draw_lines();
}

409
void Window_RelationshipsOverview::on_table_show_context(GdkEventButton* event, const Glib::WeakRef<CanvasGroupDbTable>& table_weak)
410
{
411 412 413 414
  const auto table = table_weak.get();
  if (!table)
    return;

415 416
  if(m_action_edit_fields)
  {
417
    // Disconnect the previous handler,
418 419
    // and connect a new one, with the correct table as a bound parameter:
    m_connection_edit_fields.disconnect();
420
    m_connection_edit_fields = m_action_edit_fields->signal_activate().connect(
421
      sigc::bind( sigc::mem_fun(*this, &Window_RelationshipsOverview::on_context_menu_edit_fields), table ));
422 423

    m_connection_edit_relationships.disconnect();
424
    m_connection_edit_relationships = m_action_edit_relationships->signal_activate().connect(
425
      sigc::bind( sigc::mem_fun(*this, &Window_RelationshipsOverview::on_context_menu_edit_relationships), table ));
426 427 428
  }

  if(m_context_menu)
429
    m_context_menu->popup_at_pointer((GdkEvent*)event);
430 431
}

432
void Window_RelationshipsOverview::setup_context_menu()
433
{
434
  auto action_group = Gio::SimpleActionGroup::create();
435

436 437
  m_action_edit_fields = action_group->add_action("edit-fields");
  m_action_edit_relationships = action_group->add_action("edit-relationships");
438

439
  insert_action_group("context", action_group);
440 441

  //Get the menu:
442
  auto object =
443
    m_builder->get_object("ContextMenu");
444
  auto gmenu =
445 446 447 448
    Glib::RefPtr<Gio::Menu>::cast_dynamic(object);
  if(!gmenu)
    g_warning("GMenu not found");

449
  m_context_menu = std::make_unique<Gtk::Menu>(gmenu);
450
  m_context_menu->attach_to_widget(*this);
451 452
}

453
void Window_RelationshipsOverview::on_context_menu_edit_fields(const Glib::VariantBase& /* parameter */, const Glib::WeakRef<CanvasGroupDbTable>& table_weak)
454
{
455 456 457 458
  const auto table = table_weak.get();
  if (!table)
    return;

Murray Cumming's avatar
Murray Cumming committed
459
  auto pApp = AppWindow::get_appwindow();
460 461 462 463 464 465 466 467
  if(pApp && table)
  {
    pApp->do_menu_developer_fields(*this, table->get_table_name());
    //draw_tables();
    //draw_lines();
  }
}

468
void Window_RelationshipsOverview::on_context_menu_edit_relationships(const Glib::VariantBase& /* parameter */, const Glib::WeakRef<CanvasGroupDbTable>& table_weak)
469
{
470 471 472 473
  const auto table = table_weak.get();
  if (!table)
    return;

Murray Cumming's avatar
Murray Cumming committed
474
  auto pApp = AppWindow::get_appwindow();
475 476 477 478 479 480 481 482
  if(pApp && table)
  {
    pApp->do_menu_developer_relationships(*this, table->get_table_name());
    //draw_tables();
    //draw_lines();
  }
}

483

484
void Window_RelationshipsOverview::on_scroll_value_changed()
485 486 487 488 489 490
{
  if(!m_scrolledwindow_canvas)
    return;

  double width = m_scrolledwindow_canvas->get_hadjustment()->get_page_size();
  double height = m_scrolledwindow_canvas->get_vadjustment()->get_page_size();
491 492
  //double x = m_scrolledwindow_canvas->get_hadjustment()->get_value();
  //double y = m_scrolledwindow_canvas->get_vadjustment()->get_value();
493

494 495 496 497 498 499
  //Make sure that the canvas bounds are as big as the scrollable area:
  double old_left = 0;
  double old_top = 0;
  double old_right = 0;
  double old_bottom = 0;
  m_canvas.get_bounds(old_left, old_top, old_right, old_bottom);
500

501 502 503 504 505 506 507 508 509 510
  const double old_height = old_bottom - old_top;
  const double old_width = old_right - old_left;

  if( (width > old_width) ||
      (height > old_height) )
  {
    m_canvas.set_bounds(0, 0, width, height);
  }
}

511 512 513 514 515 516 517 518
void Window_RelationshipsOverview::on_button_close()
{
  if(m_modified && get_document())
    get_document()->set_modified();

  hide();
}

519
} //namespace Glom