2 * Copyright (C) Volition, Inc. 1999. All rights reserved.
4 * All source code herein is the property of Volition, Inc. You may not sell
5 * or otherwise commercially exploit the source or things you created based on
9 // CampaignTreeView.cpp : implementation file
14 #include "campaigntreeview.h"
15 #include "campaigneditordlg.h"
16 #include "campaigntreewnd.h"
17 #include "missionparse.h"
22 static char THIS_FILE[] = __FILE__;
25 LOCAL int Bx, By, Mission_dragging = -1, Mission_dropping = -1, Context_mission;
27 int CTV_button_down = 0;
28 int Level_counts[MAX_LEVELS];
29 int Sorted[MAX_CAMPAIGN_MISSIONS];
30 campaign_tree_element Elements[MAX_CAMPAIGN_MISSIONS];
31 campaign_tree_link Links[MAX_CAMPAIGN_TREE_LINKS];
32 LOCAL CRect Dragging_rect;
33 LOCAL CSize Rect_offset, Last_draw_size;
35 /////////////////////////////////////////////////////////////////////////////
38 IMPLEMENT_DYNCREATE(campaign_tree_view, CScrollView)
40 campaign_tree_view *Campaign_tree_viewp;
42 campaign_tree_view::campaign_tree_view()
48 campaign_tree_view::~campaign_tree_view()
52 BEGIN_MESSAGE_MAP(campaign_tree_view, CScrollView)
53 //{{AFX_MSG_MAP(campaign_tree_view)
59 ON_COMMAND(ID_REMOVE_MISSION, OnRemoveMission)
60 ON_COMMAND(ID_DELETE_ROW, OnDeleteRow)
61 ON_COMMAND(ID_INSERT_ROW, OnInsertRow)
62 ON_COMMAND(ID_ADD_REPEAT, OnAddRepeat)
63 ON_COMMAND(ID_END_OF_CAMPAIGN, OnEndOfCampaign)
67 /////////////////////////////////////////////////////////////////////////////
68 // campaign_tree_view drawing
70 #define LEVEL_HEIGHT 75
71 #define CELL_WIDTH 150
72 #define CELL_TEXT_WIDTH 130
74 int campaign_tree_view::OnCreate(LPCREATESTRUCT lpCreateStruct)
76 if (CScrollView::OnCreate(lpCreateStruct) == -1)
82 void campaign_tree_view::OnDraw(CDC* pDC)
89 CPen black_pen, white_pen, red_pen, blue_pen, green_pen;
93 // setup text drawing stuff
94 pDC->SetTextAlign(TA_TOP | TA_CENTER);
95 pDC->SetTextColor(RGB(0, 0, 0));
96 pDC->SetBkMode(TRANSPARENT);
98 // figure out text box sizes
99 r = pDC->GetTextMetrics(&tm);
101 Bx = CELL_TEXT_WIDTH + 4;
102 By = tm.tmHeight + 4;
104 r = gray_brush.CreateSolidBrush(RGB(192, 192, 192));
106 pDC->FillRect(CRect(0, 0, total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT), &gray_brush);
109 r = black_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
111 r = white_pen.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
113 r = red_pen.CreatePen(PS_SOLID, 1, RGB(192, 0, 0));
115 r = blue_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 192));
117 r = green_pen.CreatePen(PS_SOLID, 1, RGB(0, 192, 0));
120 pDC->SelectObject(&black_pen);
121 // draw level seperators
122 for (i=1; i<total_levels; i++) {
123 pDC->MoveTo(0, i * LEVEL_HEIGHT - 1);
124 pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT - 1);
127 pDC->SelectObject(&white_pen);
128 for (i=1; i<total_levels; i++) {
129 pDC->MoveTo(0, i * LEVEL_HEIGHT);
130 pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT);
134 // draw edges of the whole tree rectangle
135 pDC->SelectObject(&black_pen);
136 pDC->MoveTo(0, total_levels * LEVEL_HEIGHT);
137 pDC->LineTo(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT);
138 pDC->LineTo(total_width * CELL_WIDTH, -1);
140 // draw text boxes and text
142 for (i=0; i<Campaign.num_missions; i++) {
143 x = (Campaign.missions[i].pos + 1) * CELL_WIDTH / 2;
144 y = Campaign.missions[i].level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2;
145 Elements[i].box.left = x - Bx / 2;
146 Elements[i].box.right = Elements[i].box.left + Bx;
147 Elements[i].box.top = y - By / 2;
148 Elements[i].box.bottom = Elements[i].box.top + By;
150 strcpy(str, Campaign.missions[i].name);
151 str[strlen(str) - 4] = 0; // strip extension from filename
152 GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
153 if (size.cx > CELL_TEXT_WIDTH) {
154 strcpy(str + strlen(str) - 1, "...");
155 GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
156 while (size.cx > CELL_TEXT_WIDTH) {
157 strcpy(str + strlen(str) - 4, "...");
158 GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
162 if (i == Cur_campaign_mission) {
163 pDC->SetTextColor(RGB(192, 0, 0));
164 pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(0, 0, 0), RGB(255, 255, 255));
167 pDC->SetTextColor(RGB(0, 0, 0));
168 pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(255, 255, 255), RGB(0, 0, 0));
171 pDC->TextOut(x, y - By / 2 + 2, str, strlen(str));
174 for (i=0; i<Total_links; i++) {
181 Links[i].p1.x = Elements[f].box.left + Links[i].from_pos * Bx / (Elements[f].from_links + 1);
182 Links[i].p1.y = Elements[f].box.bottom;
183 Links[i].p2.x = Elements[t].box.left + Links[i].to_pos * Bx / (Elements[t].to_links + 1);
184 Links[i].p2.y = Elements[t].box.top;
186 // if mission_loop link, select blue pen
187 if (Links[i].mission_loop) {
188 pDC->SelectObject(&blue_pen);
191 // if active link, select highlight pen (red - normal, green - mission loop)
192 if (i == Cur_campaign_link) {
193 if (Links[i].mission_loop) {
194 pDC->SelectObject(&green_pen);
196 pDC->SelectObject(&red_pen);
200 // draw a line between 'from' and 'to' mission. to might be -1 in the case of end-campaign, so
201 // don't draw line if so.
202 if ( (f != t) && ( t != -1) ) {
203 pDC->MoveTo(Links[i].p1);
204 pDC->LineTo(Links[i].p2);
207 // select (defalt) black pen
208 pDC->SelectObject(&black_pen);
211 pDC->SelectObject(&black_pen);
214 /////////////////////////////////////////////////////////////////////////////
215 // campaign_tree_view diagnostics
218 void campaign_tree_view::AssertValid() const
220 CScrollView::AssertValid();
223 void campaign_tree_view::Dump(CDumpContext& dc) const
225 CScrollView::Dump(dc);
229 /////////////////////////////////////////////////////////////////////////////
230 // campaign_tree_view message handlers
232 void campaign_tree_view::OnInitialUpdate()
234 CScrollView::OnInitialUpdate();
235 SetScrollSizes(MM_TEXT, CSize(320, 320));
238 void stuff_link_with_formula(int *link_idx, int formula, int mission_num)
240 int j, node, node2, node3;
242 if (!stricmp(CTEXT(formula), "cond")) {
246 free_one_sexp(formula);
249 Links[*link_idx].from = mission_num;
250 Links[*link_idx].sexp = CAR(node2);
251 Links[*link_idx].mission_loop_txt = NULL;
252 Links[*link_idx].mission_loop_brief_anim = NULL;
253 Links[*link_idx].mission_loop_brief_sound = NULL;
254 sexp_mark_persistent(CAR(node2));
255 free_one_sexp(node2);
257 if ( !stricmp( CTEXT(node3), "next-mission") ) {
259 for (j=0; j<Campaign.num_missions; j++)
260 if (!stricmp(CTEXT(node3), Campaign.missions[j].name))
263 if (j < Campaign.num_missions) { // mission is in campaign (you never know..)
264 Links[(*link_idx)++].to = j;
265 Elements[mission_num].from_links++;
266 Elements[j].to_links++;
269 } else if ( !stricmp( CTEXT(node3), "end-of-campaign") ) {
270 Links[(*link_idx)++].to = -1;
271 Elements[mission_num].from_links++;
274 Int3(); // bogus operator in campaign file
276 free_sexp(CDR(node2));
284 // this function should only be called right after a campaign is loaded. Calling it a second
285 // time without having loaded a campaign again will result in undefined behavior.
286 void campaign_tree_view::construct_tree()
291 // initialize mission link counts
292 for (i=0; i<Campaign.num_missions; i++) {
293 Elements[i].from_links = Elements[i].to_links = 0;
296 // analyze branching sexps and build links from them.
298 for (i=0; i<Campaign.num_missions; i++) {
300 // do main campaign path
301 stuff_link_with_formula(&link_idx, Campaign.missions[i].formula, i);
303 // do mission loop path
304 if ( Campaign.missions[i].has_mission_loop ) {
305 stuff_link_with_formula(&link_idx, Campaign.missions[i].mission_loop_formula, i);
306 Links[link_idx-1].mission_loop_txt = Campaign.missions[i].mission_loop_desc;
307 Links[link_idx-1].mission_loop_brief_anim = Campaign.missions[i].mission_loop_brief_anim;
308 Links[link_idx-1].mission_loop_brief_sound = Campaign.missions[i].mission_loop_brief_sound;
309 Links[link_idx-1].mission_loop = 1;
313 for (i=0; i<Campaign.num_missions; i++) {
317 Total_links = link_idx;
318 if (Campaign.realign_required) {
320 Campaign.realign_required = 0;
324 void campaign_tree_view::initialize()
328 total_levels = total_width = 1;
329 for (i=0; i<MAX_LEVELS; i++)
332 for (i=0; i<Campaign.num_missions; i++) {
333 z = Campaign.missions[i].level;
334 if (z + 2 > total_levels)
335 total_levels = z + 2;
338 z = (Campaign.missions[i].pos + 3) / 2;
344 SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
348 void campaign_tree_view::free_links()
352 for (i=0; i<Total_links; i++) {
353 sexp_unmark_persistent(Links[i].sexp);
354 free_sexp2(Links[i].sexp);
360 void campaign_tree_view::realign_tree()
362 int i, j, z, offset, level, pos;
364 // figure out what level each mission lies on and an initial position on that level
365 level = pos = total_width = 0;
366 for (i=0; i<Campaign.num_missions; i++) {
368 for (j=0; j<Total_links; j++)
369 if (Links[j].to == z) {
370 SDL_assert(Campaign.missions[Links[j].from].level <= Campaign.missions[z].level); // links can't go up the tree, only down
371 if (Campaign.missions[Links[j].from].level == level) {
372 level++; // force to next level in tree
378 Campaign.missions[z].level = level;
379 Campaign.missions[z].pos = pos++;
380 if (pos > total_width)
383 Level_counts[level] = pos;
384 if (!z) { // topmost node must always be alone on level
390 // now calculate the true x position of each mission
391 for (i=0; i<Campaign.num_missions; i++) {
392 offset = total_width - Level_counts[Campaign.missions[i].level];
393 Campaign.missions[i].pos = Campaign.missions[i].pos * 2 + offset;
397 void campaign_tree_view::sort_links()
399 int i, j, k, z, to_count, from_count, swap;
400 int to_list[MAX_CAMPAIGN_TREE_LINKS];
401 int from_list[MAX_CAMPAIGN_TREE_LINKS];
403 for (i=0; i<Campaign.num_missions; i++) {
404 // build list of all to and from links for one mission at a time
405 to_count = from_count = 0;
406 for (j=0; j<Total_links; j++) {
407 if ((Links[j].to == i) && (Links[j].from == i))
408 continue; // ignore 'repeat mission' links
409 if (Links[j].to == i)
410 to_list[to_count++] = j;
411 if (Links[j].from == i)
412 from_list[from_count++] = j;
415 // sort to links, left to right and top to bottom
416 for (j=0; j<to_count-1; j++)
417 for (k=j+1; k<to_count; k++) {
419 z = Campaign.missions[Links[to_list[j]].from].pos -
420 Campaign.missions[Links[to_list[k]].from].pos;
422 if (z > 0) // sort left to right
425 else if (!z) { // both have same position?
426 z = Campaign.missions[Links[to_list[j]].from].level -
427 Campaign.missions[Links[to_list[k]].from].level;
429 // see where from link position is relative to to link position
430 if (Campaign.missions[i].pos < Campaign.missions[Links[to_list[j]].from].pos) {
431 if (z > 0) // sort bottom to top
435 if (z < 0) // sort top to bottom
442 to_list[j] = to_list[k];
447 // set all links to positions
448 for (j=0; j<to_count; j++)
449 Links[to_list[j]].to_pos = j + 1;
451 // sort from links, left to right and bottom to top
452 for (j=0; j<from_count-1; j++)
453 for (k=j+1; k<from_count; k++) {
455 z = Campaign.missions[Links[from_list[j]].to].pos -
456 Campaign.missions[Links[from_list[k]].to].pos;
462 z = Campaign.missions[Links[from_list[j]].to].level -
463 Campaign.missions[Links[from_list[k]].to].level;
465 if (Campaign.missions[i].pos < Campaign.missions[Links[from_list[j]].to].pos) {
477 from_list[j] = from_list[k];
482 // set all links from positions
483 for (j=0; j<from_count; j++)
484 Links[from_list[j]].from_pos = j + 1;
488 void campaign_tree_view::OnLButtonDown(UINT nFlags, CPoint point)
498 if (nFlags & MK_CONTROL) {
499 listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
500 i = listbox->GetCurSel();
502 Mission_dropping = -1;
504 Mission_dropping = i;
508 Last_draw_size = CSize(0, 0);
509 Dragging_rect.SetRect(0, 0, 1, 1);
510 dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
513 if ( (Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop) {
514 // HACK!! UPDATE mission loop desc before changing selections
515 // save mission loop desc
516 char buffer[MISSION_DESC_LENGTH];
517 box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_MISSISON_LOOP_DESC);
518 box->GetWindowText(buffer, MISSION_DESC_LENGTH);
519 if (strlen(buffer)) {
520 if (Links[Cur_campaign_link].mission_loop_txt) {
521 free(Links[Cur_campaign_link].mission_loop_txt);
523 Links[Cur_campaign_link].mission_loop_txt = strdup(buffer);
525 Links[Cur_campaign_link].mission_loop_txt = NULL;
528 // HACK!! UPDATE mission loop desc before changing selections
529 // save mission loop desc
530 box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_ANIM);
531 box->GetWindowText(buffer, MISSION_DESC_LENGTH);
532 if (strlen(buffer)) {
533 if (Links[Cur_campaign_link].mission_loop_brief_anim) {
534 free(Links[Cur_campaign_link].mission_loop_brief_anim);
536 Links[Cur_campaign_link].mission_loop_brief_anim = strdup(buffer);
538 Links[Cur_campaign_link].mission_loop_brief_anim = NULL;
541 // HACK!! UPDATE mission loop desc before changing selections
542 // save mission loop desc
543 box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_SOUND);
544 box->GetWindowText(buffer, MISSION_DESC_LENGTH);
545 if (strlen(buffer)) {
546 if (Links[Cur_campaign_link].mission_loop_brief_sound) {
547 free(Links[Cur_campaign_link].mission_loop_brief_sound);
549 Links[Cur_campaign_link].mission_loop_brief_sound = strdup(buffer);
551 Links[Cur_campaign_link].mission_loop_brief_sound = NULL;
554 Mission_dragging = Cur_campaign_mission = Cur_campaign_link = -1;
555 for (i=0; i<Campaign.num_missions; i++)
556 if (Elements[i].box.PtInRect(point)) {
559 Mission_dragging = Cur_campaign_mission = i;
560 Dragging_rect = Elements[i].box;
561 Rect_offset = Dragging_rect.TopLeft() - point;
562 Last_draw_size = CSize(4, 4);
563 if (Campaign.missions[Cur_campaign_mission].num_goals < 0) // haven't loaded goal names yet (or notes)
564 read_mission_goal_list(Cur_campaign_mission);
566 if (Campaign.missions[Cur_campaign_mission].notes) {
567 str = convert_multiline_string(Campaign.missions[Cur_campaign_mission].notes);
568 box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
570 box->SetWindowText(str);
573 Campaign_tree_formp->mission_selected(Cur_campaign_mission);
580 Campaign_tree_formp->load_tree();
581 if (Mission_dragging != -1) {
582 CRect rect = Dragging_rect;
585 dc.DrawDragRect(rect, Last_draw_size, NULL, CSize(0, 0));
588 CScrollView::OnLButtonDown(nFlags, point);
591 void campaign_tree_view::OnMouseMove(UINT nFlags, CPoint point)
593 int i, level, pos, x, y;
600 if ((Mission_dragging >= 0) || (Mission_dropping >= 0)) {
601 if (GetCapture() != this) {
602 rect = Dragging_rect;
604 dc.DrawDragRect(rect, CSize(0, 0), rect, Last_draw_size);
605 Mission_dragging = Mission_dropping = -1;
608 for (i=0; i<Campaign.num_missions; i++)
609 if (Elements[i].box.PtInRect(point))
612 if ((i < Campaign.num_missions) && (Mission_dropping < 0)) { // on a mission box?
613 draw_size = CSize(4, 4);
614 rect = Elements[i].box;
617 level = query_level(point);
618 pos = query_pos(point);
619 if ((level < 0) || (pos < 0)) { // off table?
620 draw_size = CSize(0, 0);
621 rect = Dragging_rect;
624 draw_size = CSize(2, 2);
625 for (i=0; i<Campaign.num_missions; i++)
626 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
627 pos = query_alternate_pos(point);
631 x = pos * CELL_WIDTH / 2 - Bx / 2;
632 y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - By / 2;
633 rect.SetRect(x, y, x + Bx, y + By);
639 dc.LPtoDP(&Dragging_rect);
640 dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
641 Dragging_rect = rect;
642 Last_draw_size = draw_size;
646 CScrollView::OnMouseMove(nFlags, point);
649 void campaign_tree_view::OnLButtonUp(UINT nFlags, CPoint point)
651 int i, j, z, level, pos;
656 if (Mission_dropping >= 0) { // dropping a new mission into campaign?
657 z = Mission_dropping;
658 Mission_dropping = -1;
659 if (GetCapture() == this) {
661 dc.LPtoDP(&Dragging_rect);
662 dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
664 drop_mission(z, point);
668 } else if (Mission_dragging >= 0) { // moving position of a mission?
669 z = Mission_dragging;
670 Mission_dragging = -1;
671 if (GetCapture() == this) {
673 dc.LPtoDP(&Dragging_rect);
674 dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
675 for (i=0; i<Campaign.num_missions; i++)
676 if (Elements[i].box.PtInRect(point)) { // see if released on another mission
677 if (i == z) // released on the same mission?
680 for (j=0; j<Total_links; j++)
681 if ((Links[j].from == z) && (Links[j].to == i))
682 return; // already linked
684 if (Total_links >= MAX_CAMPAIGN_TREE_LINKS) {
685 MessageBox("Too many links exist. Can't add any more.");
689 if (Campaign.missions[z].level >= Campaign.missions[i].level) {
690 MessageBox("A branch can only be set to a mission on a lower level");
698 // at this point, we are dragging a mission to a new place
699 level = query_level(point);
700 pos = query_pos(point);
701 if ((level < 0) || (pos < 0))
704 if (!level && (get_root_mission() >= 0)) {
705 MessageBox("Can't move mission to this level. There is already a top level mission");
709 for (i=0; i<Campaign.num_missions; i++)
710 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
711 pos = query_alternate_pos(point);
715 for (i=0; i<Total_links; i++)
716 if (Links[i].to == z)
717 if (level <= Campaign.missions[Links[i].from].level) {
718 MessageBox("Can't move mission to that level, as it would be\n"
719 "above a parent mission", "Error");
724 Campaign.missions[z].level = level;
725 Campaign.missions[z].pos = pos - 1;
728 SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
730 Campaign_modified = 1;
735 CScrollView::OnLButtonUp(nFlags, point);
738 int campaign_tree_view::add_link(int from, int to)
740 if (Total_links >= MAX_CAMPAIGN_TREE_LINKS)
743 Campaign_tree_formp->load_tree(1);
744 Links[Total_links].from = from;
745 Links[Total_links].to = to;
746 Links[Total_links].sexp = Locked_sexp_true;
747 Links[Total_links].mission_loop = false;
748 Links[Total_links].mission_loop_txt = NULL;
749 Links[Total_links].mission_loop_brief_anim = NULL;
750 Links[Total_links].mission_loop_brief_sound = NULL;
753 Elements[from].from_links++;
754 Elements[to].to_links++;
758 Campaign_tree_formp->load_tree(0);
760 Campaign_modified = 1;
764 int campaign_tree_view::query_level(const CPoint& p)
768 if ((p.y < 0) || (p.y >= total_levels * LEVEL_HEIGHT))
771 level = p.y / LEVEL_HEIGHT;
772 SDL_assert((level >= 0) && (level < total_levels));
776 int campaign_tree_view::query_pos(const CPoint& p)
780 if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
783 pos = ((p.x * 4 / CELL_WIDTH) + 1) / 2;
784 SDL_assert((pos >= 0) && (pos <= total_width * 2));
788 int campaign_tree_view::query_alternate_pos(const CPoint& p)
792 if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
795 x = p.x * 4 / CELL_WIDTH;
797 if (x & 1) // odd number
802 SDL_assert((pos >= 0) && (pos <= total_width * 2));
806 DROPEFFECT campaign_tree_view::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
811 if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
812 return DROPEFFECT_NONE; // data isn't a mission filename, the only valid data to drop here
814 Mission_dropping = 1;
815 Last_draw_size = CSize(0, 0);
816 Dragging_rect.SetRect(0, 0, 1, 1);
817 dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
818 return DROPEFFECT_MOVE;
821 void campaign_tree_view::OnDragLeave()
823 CScrollView::OnDragLeave();
824 if (Mission_dropping >= 0) {
828 dc.LPtoDP(&Dragging_rect);
829 dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
830 Mission_dropping = -1;
834 DROPEFFECT campaign_tree_view::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
836 int i, level, pos, x, y;
839 DROPEFFECT r = DROPEFFECT_MOVE;
841 if (Mission_dropping < 0)
842 return DROPEFFECT_NONE;
848 level = query_level(point);
849 pos = query_pos(point);
850 if ((level < 0) || (pos < 0)) { // off table?
851 draw_size = CSize(0, 0);
852 rect = Dragging_rect;
856 draw_size = CSize(2, 2);
857 for (i=0; i<Campaign.num_missions; i++)
858 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
859 pos = query_alternate_pos(point);
863 x = pos * CELL_WIDTH / 2 - Dragging_rect.Width() / 2;
864 y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - Dragging_rect.Height() / 2;
865 rect.SetRect(x, y, x + Bx, y + By);
870 dc.LPtoDP(&Dragging_rect);
871 dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
872 Dragging_rect = rect;
873 Last_draw_size = draw_size;
878 BOOL campaign_tree_view::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point)
886 if (Mission_dropping < 0)
889 // If the dropEffect requested is not a MOVE, return FALSE to
890 // signal no drop. (No COPY into trashcan)
891 if ((dropEffect & DROPEFFECT_MOVE) != DROPEFFECT_MOVE)
898 dc.LPtoDP(&Dragging_rect);
899 dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
900 Mission_dropping = -1;
902 if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
903 return FALSE; // data isn't a mission filename, the only valid data to drop here
905 // Get text data from COleDataObject
906 hGlobal = pDataObject->GetGlobalData((unsigned short)Mission_filename_cb_format);
908 // Get pointer to data
909 pData = (LPCSTR) GlobalLock(hGlobal);
912 if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) { // Can't add any more
913 GlobalUnlock(hGlobal);
914 MessageBox("Too many missions. Can't add more to Campaign.", "Error");
918 level = query_level(point);
919 pos = query_pos(point);
920 SDL_assert((level >= 0) && (pos >= 0)); // this should be impossible
921 if (!level && (get_root_mission() >= 0)) {
922 GlobalUnlock(hGlobal);
923 MessageBox("Only 1 mission may be in the top level");
927 // check the number of players in a multiplayer campaign against the number of players
928 // in the mission that was just dropped
929 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
930 get_mission_info((char *)pData, &a_mission);
931 if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
934 sprintf( buf, "Mission \"%s\" is not a multiplayer mission", pData );
935 MessageBox(buf, "Error");
936 GlobalUnlock(hGlobal);
940 if ( Campaign.num_players != 0 ) {
941 if ( Campaign.num_players != a_mission.num_players ) {
944 sprintf(buf, "Mission \"%s\" has %d players. Campaign has %d players. Campaign will not play properly in FreeSpace", pData, a_mission.num_players, Campaign.num_players );
945 MessageBox(buf, "Warning");
948 Campaign.num_players = a_mission.num_players;
952 Elements[Campaign.num_missions].from_links = Elements[Campaign.num_missions].to_links = 0;
953 cm = &(Campaign.missions[Campaign.num_missions++]);
954 cm->name = strdup(pData);
955 cm->formula = Locked_sexp_true;
958 cm->briefing_cutscene[0] = 0;
959 for (i=0; i<Campaign.num_missions - 1; i++)
960 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
961 pos = query_alternate_pos(point);
967 correct_position(Campaign.num_missions - 1);
969 SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
972 // update and reinitialize dialog items
973 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
974 Campaign_tree_formp->update();
975 Campaign_tree_formp->initialize(0);
978 // Unlock memory - Send dropped text into the "bit-bucket"
979 GlobalUnlock(hGlobal);
980 Campaign_modified = 1;
985 void campaign_tree_view::drop_mission(int m, CPoint point)
987 char name[MAX_FILENAME_LEN + 1];
988 int i, item, level, pos;
993 level = query_level(point);
994 pos = query_pos(point);
995 SDL_assert((level >= 0) && (pos >= 0)); // this should be impossible
997 listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
998 item = listbox->GetCurSel();
999 if (item == LB_ERR) {
1000 MessageBox("Select a mission from listbox to add.", "Error");
1004 if (listbox->GetTextLen(item) > MAX_FILENAME_LEN) {
1007 sprintf(buf, "Filename is too long. Must be %d or less characters.", MAX_FILENAME_LEN);
1008 MessageBox(buf, "Error");
1009 return; // filename is too long. Would overflow our buffer
1012 // grab the filename selected from the listbox
1013 listbox->GetText(item, name);
1015 if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) { // Can't add any more
1016 MessageBox("Too many missions. Can't add more to Campaign.", "Error");
1020 if (!level && (get_root_mission() >= 0)) {
1021 MessageBox("Only 1 mission may be in the top level");
1025 // check the number of players in a multiplayer campaign against the number of players
1026 // in the mission that was just dropped
1027 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1028 get_mission_info(name, &a_mission);
1029 if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
1032 sprintf(buf, "Mission \"%s\" is not a multiplayer mission", name);
1033 MessageBox(buf, "Error");
1037 if (Campaign.num_players != 0) {
1038 if (Campaign.num_players != a_mission.num_players) {
1041 sprintf(buf, "Mission \"%s\" has %d players. Campaign has %d players. Campaign will not play properly in FreeSpace", name, a_mission.num_players, Campaign.num_players );
1042 MessageBox(buf, "Warning");
1046 Campaign.num_players = a_mission.num_players;
1050 Elements[Campaign.num_missions].from_links = Elements[Campaign.num_missions].to_links = 0;
1051 cm = &(Campaign.missions[Campaign.num_missions++]);
1052 cm->name = strdup(name);
1053 cm->formula = Locked_sexp_true;
1056 cm->briefing_cutscene[0] = 0;
1057 for (i=0; i<Campaign.num_missions - 1; i++)
1058 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
1059 pos = query_alternate_pos(point);
1065 correct_position(Campaign.num_missions - 1);
1067 SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1070 // update and reinitialize dialog items
1071 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1072 Campaign_tree_formp->update();
1073 Campaign_tree_formp->initialize(0);
1076 listbox->DeleteString(item);
1077 Campaign_modified = 1;
1081 void campaign_tree_view::sort_elements()
1085 for (i=0; i<Campaign.num_missions; i++)
1088 // sort the tree, so realignment will work property
1089 for (i=1; i<Campaign.num_missions; i++) {
1091 for (j=i-1; j>=0; j--) {
1093 if ((Campaign.missions[s1].level > Campaign.missions[s2].level) ||
1094 ((Campaign.missions[s1].level == Campaign.missions[s2].level) &&
1095 (Campaign.missions[s1].pos > Campaign.missions[s2].pos)))
1105 void campaign_tree_view::correct_position(int num)
1109 // move missions down if required
1110 if (Campaign.missions[num].level + 2 > total_levels)
1111 total_levels = Campaign.missions[num].level + 2;
1113 for (i=0; i<Total_links; i++)
1114 if (Links[i].from == num) {
1116 if ( (num != z) && (Campaign.missions[num].level >= Campaign.missions[z].level) ) {
1117 Campaign.missions[z].level = Campaign.missions[num].level + 1;
1118 correct_position(z);
1122 // space out horizontally to avoid overlap of missions
1123 horizontally_align_mission(num, -1);
1124 horizontally_align_mission(num, 1);
1127 void campaign_tree_view::horizontally_align_mission(int num, int dir)
1131 if ((Campaign.missions[num].pos == -1) || (Campaign.missions[num].pos + 1 == total_width * 2)) { // need to expand total_width
1132 for (i=0; i<Campaign.num_missions; i++)
1133 Campaign.missions[i].pos++;
1138 for (i=0; i<Campaign.num_missions; i++) {
1142 if (Campaign.missions[i].level == Campaign.missions[num].level) {
1143 z = Campaign.missions[i].pos - Campaign.missions[num].pos;
1145 if (!z || (z == -1)) {
1146 Campaign.missions[i].pos = Campaign.missions[num].pos - 2;
1147 horizontally_align_mission(i, -1);
1151 if (!z || (z == 1)) {
1152 Campaign.missions[i].pos = Campaign.missions[num].pos + 2;
1153 horizontally_align_mission(i, 1);
1160 void campaign_tree_view::delete_link(int num)
1162 SDL_assert((num >= 0) && (num < Total_links));
1163 if (Links[num].from != Links[num].to) {
1164 Elements[Links[num].from].from_links--;
1165 Elements[Links[num].to].to_links--;
1168 sexp_unmark_persistent(Links[num].sexp);
1169 free_sexp2(Links[num].sexp);
1170 while (num < Total_links - 1) {
1171 Links[num] = Links[num + 1];
1178 Campaign_modified = 1;
1182 int campaign_tree_view::get_root_mission()
1186 for (i=0; i<Campaign.num_missions; i++)
1187 if (!Campaign.missions[i].level)
1193 void campaign_tree_view::OnContextMenu(CWnd* pWnd, CPoint point)
1204 for (i=0; i<Campaign.num_missions; i++)
1205 if (Elements[i].box.PtInRect(p))
1208 if (i < Campaign.num_missions) { // clicked on a mission
1209 Context_mission = i;
1210 if (menu.LoadMenu(IDR_CPGN_VIEW_ON)) {
1211 popup = menu.GetSubMenu(0);
1213 popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1217 Context_mission = query_level(p);
1218 if ((Context_mission >= 0) && (Context_mission < total_levels))
1219 if (menu.LoadMenu(IDR_CPGN_VIEW_OFF)) {
1220 popup = menu.GetSubMenu(0);
1222 popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1227 void campaign_tree_view::OnRemoveMission()
1229 remove_mission(Context_mission);
1233 // for multiplayer missions, update the data and reiniailize the dialog -- the number of player
1234 // in the mission might have changed because of deletion of the first mission
1235 if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1236 Campaign_tree_formp->update();
1237 Campaign_tree_formp->initialize();
1241 void campaign_tree_view::remove_mission(int m)
1247 Campaign_tree_formp->m_filelist.AddString(Campaign.missions[m].name);
1249 z = --Campaign.num_missions;
1252 if ((Links[i].from == m) || (Links[i].to == m))
1254 if (Links[i].from == z)
1256 if (Links[i].to == z)
1260 Elements[m] = Elements[z];
1261 Campaign.missions[m] = Campaign.missions[z];
1262 if (m == Cur_campaign_mission) {
1263 Cur_campaign_mission = -1;
1264 box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
1266 box->SetWindowText("");
1268 Campaign_tree_formp->load_tree(0);
1271 Campaign_modified = 1;
1274 void campaign_tree_view::OnDeleteRow()
1278 if (!Context_mission) {
1279 MessageBox("Can't delete the top level");
1283 for (i=z=0; i<Campaign.num_missions; i++)
1284 if (Campaign.missions[i].level == Context_mission)
1288 z = MessageBox("Deleting row will remove all missions on this row", "Notice", MB_ICONEXCLAMATION | MB_OKCANCEL);
1294 if (Campaign.missions[i].level == Context_mission)
1297 for (i=0; i<Campaign.num_missions; i++)
1298 if (Campaign.missions[i].level > Context_mission)
1299 Campaign.missions[i].level--;
1302 SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1305 Campaign_modified = 1;
1308 void campaign_tree_view::OnInsertRow()
1312 for (i=0; i<Campaign.num_missions; i++)
1313 if (Campaign.missions[i].level >= Context_mission)
1314 Campaign.missions[i].level++;
1317 SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1320 Campaign_modified = 1;
1323 void campaign_tree_view::OnAddRepeat()
1325 if (add_link(Context_mission, Context_mission)) {
1326 MessageBox("Too many links exist. Can't add any more.");
1331 void campaign_tree_view::OnEndOfCampaign()
1333 if ( add_link(Context_mission, -1) ) {
1334 MessageBox("Too many links exist. Cannot add any more.");