]> icculus.org git repositories - taylor/freespace2.git/blob - src/fred2/campaigntreeview.cpp
fix issue with looping audio streams
[taylor/freespace2.git] / src / fred2 / campaigntreeview.cpp
1 /*
2  * Copyright (C) Volition, Inc. 1999.  All rights reserved.
3  *
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
6  * the source.
7  */
8
9 // CampaignTreeView.cpp : implementation file
10 //
11
12 #include "stdafx.h"
13 #include "fred.h"
14 #include "campaigntreeview.h"
15 #include "campaigneditordlg.h"
16 #include "campaigntreewnd.h"
17 #include "missionparse.h"
18
19 #ifdef _DEBUG
20 #define new DEBUG_NEW
21 #undef THIS_FILE
22 static char THIS_FILE[] = __FILE__;
23 #endif
24
25 LOCAL int Bx, By, Mission_dragging = -1, Mission_dropping = -1, Context_mission;
26 int Total_links = 0;
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;
34
35 /////////////////////////////////////////////////////////////////////////////
36 // campaign_tree_view
37
38 IMPLEMENT_DYNCREATE(campaign_tree_view, CScrollView)
39
40 campaign_tree_view *Campaign_tree_viewp;
41
42 campaign_tree_view::campaign_tree_view()
43 {
44         total_levels = 1;
45         total_width = 1;
46 }
47
48 campaign_tree_view::~campaign_tree_view()
49 {
50 }
51
52 BEGIN_MESSAGE_MAP(campaign_tree_view, CScrollView)
53         //{{AFX_MSG_MAP(campaign_tree_view)
54         ON_WM_LBUTTONDOWN()
55         ON_WM_MOUSEMOVE()
56         ON_WM_LBUTTONUP()
57         ON_WM_CREATE()
58         ON_WM_CONTEXTMENU()
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)
64         //}}AFX_MSG_MAP
65 END_MESSAGE_MAP()
66
67 /////////////////////////////////////////////////////////////////////////////
68 // campaign_tree_view drawing
69
70 #define LEVEL_HEIGHT            75
71 #define CELL_WIDTH              150
72 #define CELL_TEXT_WIDTH 130
73
74 int campaign_tree_view::OnCreate(LPCREATESTRUCT lpCreateStruct) 
75 {
76         if (CScrollView::OnCreate(lpCreateStruct) == -1)
77                 return -1;
78
79         return 0;
80 }
81
82 void campaign_tree_view::OnDraw(CDC* pDC)
83 {
84         char str[256];
85         int i, x, y, f, t;
86         BOOL r;
87         CSize size;
88         CRect rect;
89         CPen black_pen, white_pen, red_pen, blue_pen, green_pen;
90         CBrush gray_brush;
91         TEXTMETRIC tm;
92
93         // setup text drawing stuff
94         pDC->SetTextAlign(TA_TOP | TA_CENTER);
95         pDC->SetTextColor(RGB(0, 0, 0));
96         pDC->SetBkMode(TRANSPARENT);
97
98         // figure out text box sizes
99         r = pDC->GetTextMetrics(&tm);
100         SDL_assert(r);
101         Bx = CELL_TEXT_WIDTH + 4;
102         By = tm.tmHeight + 4;
103
104         r = gray_brush.CreateSolidBrush(RGB(192, 192, 192));
105         SDL_assert(r);
106         pDC->FillRect(CRect(0, 0, total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT), &gray_brush);
107
108         // create pens
109         r = black_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
110         SDL_assert(r);
111         r = white_pen.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
112         SDL_assert(r);
113         r = red_pen.CreatePen(PS_SOLID, 1, RGB(192, 0, 0));
114         SDL_assert(r);
115         r = blue_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 192));
116         SDL_assert(r);
117         r = green_pen.CreatePen(PS_SOLID, 1, RGB(0, 192, 0));
118         SDL_assert(r);
119
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);
125         }
126
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);
131         }
132
133
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);
139
140         // draw text boxes and text
141
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;
149   
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);
159                         }
160                 }
161
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));
165
166                 } else {
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));
169                 }
170
171                 pDC->TextOut(x, y - By / 2 + 2, str, strlen(str));
172         }
173
174         for (i=0; i<Total_links; i++) {
175                 f = Links[i].from;
176                 t = Links[i].to;
177
178                 if (t == -1) {
179                         continue;
180                 }
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;
185
186                 // if mission_loop link, select blue pen
187                 if (Links[i].mission_loop) {
188                         pDC->SelectObject(&blue_pen);
189                 }
190
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);
195                         } else {
196                                 pDC->SelectObject(&red_pen);
197                         }
198                 }
199
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);
205                 }
206
207                 // select (defalt) black pen
208                 pDC->SelectObject(&black_pen);
209         }
210
211         pDC->SelectObject(&black_pen);
212 }
213
214 /////////////////////////////////////////////////////////////////////////////
215 // campaign_tree_view diagnostics
216
217 #ifdef _DEBUG
218 void campaign_tree_view::AssertValid() const
219 {
220         CScrollView::AssertValid();
221 }
222
223 void campaign_tree_view::Dump(CDumpContext& dc) const
224 {
225         CScrollView::Dump(dc);
226 }
227 #endif //_DEBUG
228
229 /////////////////////////////////////////////////////////////////////////////
230 // campaign_tree_view message handlers
231
232 void campaign_tree_view::OnInitialUpdate() 
233 {
234         CScrollView::OnInitialUpdate();
235         SetScrollSizes(MM_TEXT, CSize(320, 320));
236 }
237
238 void stuff_link_with_formula(int *link_idx, int formula, int mission_num)
239 {
240         int j, node, node2, node3;
241         if (formula >= 0) {
242                 if (!stricmp(CTEXT(formula), "cond")) {
243                         // sexp is valid
244
245                         node = CDR(formula);
246                         free_one_sexp(formula);
247                         while (node != -1) {
248                                 node2 = CAR(node);
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);
256                                 node3 = CADR(node2);
257                                 if ( !stricmp( CTEXT(node3), "next-mission") ) {
258                                         node3 = CDR(node3);
259                                         for (j=0; j<Campaign.num_missions; j++)
260                                                 if (!stricmp(CTEXT(node3), Campaign.missions[j].name))
261                                                         break;
262
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++;
267                                         }
268
269                                 } else if ( !stricmp( CTEXT(node3), "end-of-campaign") ) {
270                                         Links[(*link_idx)++].to = -1;
271                                         Elements[mission_num].from_links++;
272
273                                 } else
274                                         Int3();                 // bogus operator in campaign file
275
276                                 free_sexp(CDR(node2));
277                                 free_one_sexp(node);
278                                 node = CDR(node);
279                         }
280                 }
281         }
282 }
283
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()
287 {
288         int i;
289         free_links();
290
291         // initialize mission link counts
292         for (i=0; i<Campaign.num_missions; i++) {
293                 Elements[i].from_links = Elements[i].to_links = 0;
294         }
295
296         // analyze branching sexps and build links from them.
297         int link_idx = 0;
298         for (i=0; i<Campaign.num_missions; i++) {
299
300                 // do main campaign path
301                 stuff_link_with_formula(&link_idx, Campaign.missions[i].formula, i);
302
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;
310                 }
311         }
312
313         for (i=0; i<Campaign.num_missions; i++) {
314                 Sorted[i] = i;
315         }
316
317         Total_links = link_idx;
318         if (Campaign.realign_required) {
319                 realign_tree();
320                 Campaign.realign_required = 0;
321         }
322 }
323
324 void campaign_tree_view::initialize()
325 {
326         int i, z;
327
328         total_levels = total_width = 1;
329         for (i=0; i<MAX_LEVELS; i++)
330                 Level_counts[i] = 0;
331
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;
336
337                 Level_counts[z]++;
338                 z = (Campaign.missions[i].pos + 3) / 2;
339                 if (z > total_width)
340                         total_width = z;
341         }
342
343         sort_links();
344         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
345         Invalidate();
346 }
347
348 void campaign_tree_view::free_links()
349 {
350         int i;
351
352         for (i=0; i<Total_links; i++) {
353                 sexp_unmark_persistent(Links[i].sexp);
354                 free_sexp2(Links[i].sexp);
355         }
356
357         Total_links = 0;
358 }
359
360 void campaign_tree_view::realign_tree()
361 {
362         int i, j, z, offset, level, pos;
363
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++) {
367                 z = Sorted[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
373                                         pos = 0;
374                                         break;
375                                 }
376                         }
377
378                 Campaign.missions[z].level = level;
379                 Campaign.missions[z].pos = pos++;
380                 if (pos > total_width)
381                         total_width = pos;
382
383                 Level_counts[level] = pos;
384                 if (!z) {  // topmost node must always be alone on level
385                         level++;
386                         pos = 0;
387                 }
388         }
389
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;
394         }
395 }
396
397 void campaign_tree_view::sort_links()
398 {
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];
402
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;
413                 }
414
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++) {
418                                 swap = 0;
419                                 z = Campaign.missions[Links[to_list[j]].from].pos -
420                                         Campaign.missions[Links[to_list[k]].from].pos;
421
422                                 if (z > 0)  // sort left to right
423                                         swap = 1;
424
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;
428
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
432                                                         swap = 1;
433
434                                         } else {
435                                                 if (z < 0) // sort top to bottom
436                                                         swap = 1;
437                                         }
438                                 }
439
440                                 if (swap) {
441                                         z = to_list[j];
442                                         to_list[j] = to_list[k];
443                                         to_list[k] = z;
444                                 }
445                         }
446
447                 // set all links to positions
448                 for (j=0; j<to_count; j++)
449                         Links[to_list[j]].to_pos = j + 1;
450
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++) {
454                                 swap = 0;
455                                 z = Campaign.missions[Links[from_list[j]].to].pos -
456                                         Campaign.missions[Links[from_list[k]].to].pos;
457
458                                 if (z > 0)
459                                         swap = 1;
460                                         
461                                 else if (!z) {
462                                         z = Campaign.missions[Links[from_list[j]].to].level -
463                                                 Campaign.missions[Links[from_list[k]].to].level;
464
465                                         if (Campaign.missions[i].pos < Campaign.missions[Links[from_list[j]].to].pos) {
466                                                 if (z < 0)
467                                                         swap = 1;
468
469                                         } else {
470                                                 if (z > 0)
471                                                         swap = 1;
472                                         }
473                                 }
474
475                                 if (swap) {
476                                         z = from_list[j];
477                                         from_list[j] = from_list[k];
478                                         from_list[k] = z;
479                                 }
480                         }
481
482                 // set all links from positions
483                 for (j=0; j<from_count; j++)
484                         Links[from_list[j]].from_pos = j + 1;
485         }
486 }
487
488 void campaign_tree_view::OnLButtonDown(UINT nFlags, CPoint point) 
489 {
490         int i;
491         CString str;
492         CEdit *box;
493         CListBox *listbox;
494         CClientDC dc(this);
495
496         OnPrepareDC(&dc);
497         dc.DPtoLP(&point);
498         if (nFlags & MK_CONTROL) {
499                 listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
500                 i = listbox->GetCurSel();
501
502                 Mission_dropping = -1;
503                 if (i != LB_ERR) {
504                         Mission_dropping = i;
505                         SetCapture();
506                 }
507
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));
511
512         } else {
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);
522                                 }
523                                 Links[Cur_campaign_link].mission_loop_txt = strdup(buffer);
524                         } else {
525                                 Links[Cur_campaign_link].mission_loop_txt = NULL;
526                         }
527
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);
535                                 }
536                                 Links[Cur_campaign_link].mission_loop_brief_anim = strdup(buffer);
537                         } else {
538                                 Links[Cur_campaign_link].mission_loop_brief_anim = NULL;
539                         }
540
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);
548                                 }
549                                 Links[Cur_campaign_link].mission_loop_brief_sound = strdup(buffer);
550                         } else {
551                                 Links[Cur_campaign_link].mission_loop_brief_sound = NULL;
552                         }
553                 }
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)) {
557                                 SetCapture();
558
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);
565
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);
569                                         if (box)
570                                                 box->SetWindowText(str);
571                                 }
572
573                                 Campaign_tree_formp->mission_selected(Cur_campaign_mission);
574                                 break;
575                         }
576         }
577         
578         Invalidate();
579         UpdateWindow();
580         Campaign_tree_formp->load_tree();
581         if (Mission_dragging != -1) {
582                 CRect rect = Dragging_rect;
583
584                 dc.LPtoDP(&rect);
585                 dc.DrawDragRect(rect, Last_draw_size, NULL, CSize(0, 0));
586         }
587
588         CScrollView::OnLButtonDown(nFlags, point);
589 }
590
591 void campaign_tree_view::OnMouseMove(UINT nFlags, CPoint point)
592 {
593         int i, level, pos, x, y;
594         CSize draw_size;
595         CRect rect, r1;
596         CClientDC dc(this);
597
598         OnPrepareDC(&dc);
599         dc.DPtoLP(&point);
600         if ((Mission_dragging >= 0) || (Mission_dropping >= 0)) {
601                 if (GetCapture() != this) {
602                         rect = Dragging_rect;
603                         dc.LPtoDP(&rect);
604                         dc.DrawDragRect(rect, CSize(0, 0), rect, Last_draw_size);
605                         Mission_dragging = Mission_dropping = -1;
606
607                 } else {
608                         for (i=0; i<Campaign.num_missions; i++)
609                                 if (Elements[i].box.PtInRect(point))
610                                         break;
611
612                         if ((i < Campaign.num_missions) && (Mission_dropping < 0)) {  // on a mission box?
613                                 draw_size = CSize(4, 4);
614                                 rect = Elements[i].box;
615
616                         } else {
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;
622
623                                 } else {
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);
628                                                         break;
629                                                 }
630
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);
634                                 }
635                         }
636
637                         r1 = rect;
638                         dc.LPtoDP(&r1);
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;
643                 }
644         }
645
646         CScrollView::OnMouseMove(nFlags, point);
647 }
648
649 void campaign_tree_view::OnLButtonUp(UINT nFlags, CPoint point) 
650 {
651         int i, j, z, level, pos;
652         CClientDC dc(this);
653
654         OnPrepareDC(&dc);
655         dc.DPtoLP(&point);
656         if (Mission_dropping >= 0) {  // dropping a new mission into campaign?
657                 z = Mission_dropping;
658                 Mission_dropping = -1;
659                 if (GetCapture() == this) {
660                         ReleaseCapture();
661                         dc.LPtoDP(&Dragging_rect);
662                         dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
663
664                         drop_mission(z, point);
665                         return;
666                 }
667
668         } else if (Mission_dragging >= 0) {  // moving position of a mission?
669                 z = Mission_dragging;
670                 Mission_dragging = -1;
671                 if (GetCapture() == this) {
672                         ReleaseCapture();
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?
678                                                 return;
679
680                                         for (j=0; j<Total_links; j++)
681                                                 if ((Links[j].from == z) && (Links[j].to == i))
682                                                         return;  // already linked
683
684                                         if (Total_links >= MAX_CAMPAIGN_TREE_LINKS) {
685                                                 MessageBox("Too many links exist.  Can't add any more.");
686                                                 return;
687                                         }
688
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");
691                                                 return;
692                                         }
693
694                                         add_link(z, i);
695                                         return;
696                                 }
697
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))
702                                 return;
703
704                         if (!level && (get_root_mission() >= 0)) {
705                                 MessageBox("Can't move mission to this level.  There is already a top level mission");
706                                 return;
707                         }
708
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);
712                                         break;
713                                 }
714
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");
720
721                                                 return;
722                                         }
723
724                         Campaign.missions[z].level = level;
725                         Campaign.missions[z].pos = pos - 1;
726                         correct_position(z);
727                         sort_links();
728                         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
729                         Invalidate();
730                         Campaign_modified = 1;
731                         return;
732                 }
733         }
734         
735         CScrollView::OnLButtonUp(nFlags, point);
736 }
737
738 int campaign_tree_view::add_link(int from, int to)
739 {
740         if (Total_links >= MAX_CAMPAIGN_TREE_LINKS)
741                 return -1;
742
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;
751         Total_links++;
752         if (from != to) {
753                 Elements[from].from_links++;
754                 Elements[to].to_links++;
755         }
756
757         sort_links();
758         Campaign_tree_formp->load_tree(0);
759         Invalidate();
760         Campaign_modified = 1;
761         return 0;
762 }
763
764 int campaign_tree_view::query_level(const CPoint& p)
765 {
766         int level;
767
768         if ((p.y < 0) || (p.y >= total_levels * LEVEL_HEIGHT))
769                 return -1;
770
771         level = p.y / LEVEL_HEIGHT;
772         SDL_assert((level >= 0) && (level < total_levels));
773         return level;
774 }
775
776 int campaign_tree_view::query_pos(const CPoint& p)
777 {
778         int pos;
779         
780         if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
781                 return -1;
782
783         pos = ((p.x * 4 / CELL_WIDTH) + 1) / 2;
784         SDL_assert((pos >= 0) && (pos <= total_width * 2));
785         return pos;
786 }
787
788 int campaign_tree_view::query_alternate_pos(const CPoint& p)
789 {
790         int x, pos;
791         
792         if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
793                 return -1;
794
795         x = p.x * 4 / CELL_WIDTH;
796         pos = (x + 1) / 2;
797         if (x & 1) // odd number
798                 pos--;
799         else  // even number
800                 pos++;
801
802         SDL_assert((pos >= 0) && (pos <= total_width * 2));
803         return pos;
804 }
805 /*
806 DROPEFFECT campaign_tree_view::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
807 {
808         CClientDC dc(this);
809         OnPrepareDC(&dc);
810
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
813
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;
819 }
820
821 void campaign_tree_view::OnDragLeave()
822 {
823         CScrollView::OnDragLeave();
824         if (Mission_dropping >= 0) {
825                 CClientDC dc(this);
826                 OnPrepareDC(&dc);
827
828                 dc.LPtoDP(&Dragging_rect);
829                 dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
830                 Mission_dropping = -1;
831         }
832 }
833
834 DROPEFFECT campaign_tree_view::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) 
835 {
836         int i, level, pos, x, y;
837         CSize draw_size;
838         CRect rect, r1;
839         DROPEFFECT r = DROPEFFECT_MOVE;
840
841         if (Mission_dropping < 0)
842                 return DROPEFFECT_NONE;
843
844         CClientDC dc(this);
845         OnPrepareDC(&dc);
846         dc.DPtoLP(&point);
847
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;
853                 r = DROPEFFECT_NONE;
854
855         } else {
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);
860                                 break;
861                         }
862
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);
866         }
867
868         r1 = rect;
869         dc.LPtoDP(&r1);
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;
874
875         return r;
876 }
877
878 BOOL campaign_tree_view::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) 
879 {
880         int i, level, pos;
881         cmission *cm;
882         HGLOBAL hGlobal;
883         LPCSTR pData;
884         mission a_mission;
885
886         if (Mission_dropping < 0)
887                 return FALSE;
888
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)
892                 return FALSE;
893
894         CClientDC dc(this);
895         OnPrepareDC(&dc);
896         dc.DPtoLP(&point);
897
898         dc.LPtoDP(&Dragging_rect);
899         dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
900         Mission_dropping = -1;
901
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
904
905         // Get text data from COleDataObject
906         hGlobal = pDataObject->GetGlobalData((unsigned short)Mission_filename_cb_format);
907
908         // Get pointer to data
909         pData = (LPCSTR) GlobalLock(hGlobal);
910         ASSERT(pData);
911
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");
915                 return FALSE;
916         }
917
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");
924                 return FALSE;
925         }
926
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) ) {
932                         char buf[256];
933
934                         sprintf( buf, "Mission \"%s\" is not a multiplayer mission", pData );
935                         MessageBox(buf, "Error");
936                         GlobalUnlock(hGlobal);
937                         return FALSE;
938                 }
939
940                 if ( Campaign.num_players != 0 ) {
941                         if ( Campaign.num_players != a_mission.num_players ) {
942                                 char buf[512];
943
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");
946                         }
947                 } else {
948                         Campaign.num_players = a_mission.num_players;
949                 }
950         }
951
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;
956         cm->num_goals = -1;
957         cm->notes = NULL;
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);
962                         break;
963                 }
964
965         cm->level = level;
966         cm->pos = pos - 1;
967         correct_position(Campaign.num_missions - 1);
968         sort_links();
969         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
970         Invalidate();
971
972         // update and reinitialize dialog items
973         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
974                 Campaign_tree_formp->update();
975                 Campaign_tree_formp->initialize(0);
976         }
977
978         // Unlock memory - Send dropped text into the "bit-bucket"
979         GlobalUnlock(hGlobal);
980         Campaign_modified = 1;
981         return TRUE;
982 }
983 */
984
985 void campaign_tree_view::drop_mission(int m, CPoint point)
986 {
987         char name[MAX_FILENAME_LEN + 1];
988         int i, item, level, pos;
989         cmission *cm;
990         mission a_mission;
991         CListBox *listbox;
992
993         level = query_level(point);
994         pos = query_pos(point);
995         SDL_assert((level >= 0) && (pos >= 0));  // this should be impossible
996
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");
1001                 return;
1002         }
1003
1004         if (listbox->GetTextLen(item) > MAX_FILENAME_LEN) {
1005                 char buf[256];
1006
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
1010         }
1011
1012         // grab the filename selected from the listbox
1013         listbox->GetText(item, name);
1014
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");
1017                 return;
1018         }
1019
1020         if (!level && (get_root_mission() >= 0)) {
1021                 MessageBox("Only 1 mission may be in the top level");
1022                 return;
1023         }
1024
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) ) {
1030                         char buf[256];
1031
1032                         sprintf(buf, "Mission \"%s\" is not a multiplayer mission", name);
1033                         MessageBox(buf, "Error");
1034                         return;
1035                 }
1036
1037                 if (Campaign.num_players != 0) {
1038                         if (Campaign.num_players != a_mission.num_players) {
1039                                 char buf[512];
1040
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");
1043                         }
1044
1045                 } else {
1046                         Campaign.num_players = a_mission.num_players;
1047                 }
1048         }
1049
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;
1054         cm->num_goals = -1;
1055         cm->notes = NULL;
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);
1060                         break;
1061                 }
1062
1063         cm->level = level;
1064         cm->pos = pos - 1;
1065         correct_position(Campaign.num_missions - 1);
1066         sort_links();
1067         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1068         Invalidate();
1069
1070         // update and reinitialize dialog items
1071         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1072                 Campaign_tree_formp->update();
1073                 Campaign_tree_formp->initialize(0);
1074         }
1075
1076         listbox->DeleteString(item);
1077         Campaign_modified = 1;
1078         return;
1079 }
1080
1081 void campaign_tree_view::sort_elements()
1082 {
1083         int i, j, s1, s2;
1084
1085         for (i=0; i<Campaign.num_missions; i++)
1086                 Sorted[i] = i;
1087
1088         // sort the tree, so realignment will work property
1089         for (i=1; i<Campaign.num_missions; i++) {
1090                 s1 = Sorted[i];
1091                 for (j=i-1; j>=0; j--) {
1092                         s2 = Sorted[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)))
1096                                         break;
1097
1098                         Sorted[j + 1] = s2;
1099                 }
1100
1101                 Sorted[j + 1] = s1;
1102         }
1103 }
1104
1105 void campaign_tree_view::correct_position(int num)
1106 {
1107         int i, z;
1108
1109         // move missions down if required
1110         if (Campaign.missions[num].level + 2 > total_levels)
1111                 total_levels = Campaign.missions[num].level + 2;
1112
1113         for (i=0; i<Total_links; i++)
1114                 if (Links[i].from == num) {
1115                         z = Links[i].to;
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);
1119                         }
1120                 }
1121
1122         // space out horizontally to avoid overlap of missions
1123         horizontally_align_mission(num, -1);
1124         horizontally_align_mission(num, 1);
1125 }
1126
1127 void campaign_tree_view::horizontally_align_mission(int num, int dir)
1128 {
1129         int i, z;
1130
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++;
1134
1135                 total_width++;
1136         }
1137
1138         for (i=0; i<Campaign.num_missions; i++) {
1139                 if (i == num)
1140                         continue;
1141
1142                 if (Campaign.missions[i].level == Campaign.missions[num].level) {
1143                         z = Campaign.missions[i].pos - Campaign.missions[num].pos;
1144                         if (dir < 0) {
1145                                 if (!z || (z == -1)) {
1146                                         Campaign.missions[i].pos = Campaign.missions[num].pos - 2;
1147                                         horizontally_align_mission(i, -1);
1148                                 }
1149
1150                         } else {
1151                                 if (!z || (z == 1)) {
1152                                         Campaign.missions[i].pos = Campaign.missions[num].pos + 2;
1153                                         horizontally_align_mission(i, 1);
1154                                 }
1155                         }
1156                 }
1157         }
1158 }
1159
1160 void campaign_tree_view::delete_link(int num)
1161 {
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--;
1166         }
1167
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];
1172                 num++;
1173         }
1174
1175         Total_links--;
1176         sort_links();
1177         Invalidate();
1178         Campaign_modified = 1;
1179         return;
1180 }
1181
1182 int campaign_tree_view::get_root_mission()
1183 {
1184         int i;
1185
1186         for (i=0; i<Campaign.num_missions; i++)
1187                 if (!Campaign.missions[i].level)
1188                         return i;
1189
1190         return -1;
1191 }
1192
1193 void campaign_tree_view::OnContextMenu(CWnd* pWnd, CPoint point) 
1194 {
1195         int i;
1196         CMenu menu, *popup;
1197         CPoint p = point;
1198         CClientDC dc(this);
1199
1200         OnPrepareDC(&dc);
1201         dc.DPtoLP(&p);
1202
1203         ScreenToClient(&p);
1204         for (i=0; i<Campaign.num_missions; i++)
1205                 if (Elements[i].box.PtInRect(p))
1206                         break;
1207
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);
1212                         ASSERT(popup);
1213                         popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1214                 }
1215
1216         } else {
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);
1221                                 ASSERT(popup);
1222                                 popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1223                         }
1224         }
1225 }
1226
1227 void campaign_tree_view::OnRemoveMission() 
1228 {
1229         remove_mission(Context_mission);
1230         Invalidate();
1231         UpdateWindow();
1232
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();
1238         }
1239 }
1240
1241 void campaign_tree_view::remove_mission(int m)
1242 {
1243         int i, z;
1244         CEdit *box;
1245
1246         SDL_assert(m >= 0);
1247         Campaign_tree_formp->m_filelist.AddString(Campaign.missions[m].name);
1248
1249         z = --Campaign.num_missions;
1250         i = Total_links;
1251         while (i--) {
1252                 if ((Links[i].from == m) || (Links[i].to == m))
1253                         delete_link(i);
1254                 if (Links[i].from == z)
1255                         Links[i].from = m;
1256                 if (Links[i].to == z)
1257                         Links[i].to = m;
1258         }
1259
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);
1265                 if (box)
1266                         box->SetWindowText("");
1267
1268                 Campaign_tree_formp->load_tree(0);
1269         }
1270
1271         Campaign_modified = 1;
1272 }
1273
1274 void campaign_tree_view::OnDeleteRow() 
1275 {
1276         int i, z;
1277
1278         if (!Context_mission) {
1279                 MessageBox("Can't delete the top level");
1280                 return;
1281         }
1282
1283         for (i=z=0; i<Campaign.num_missions; i++)
1284                 if (Campaign.missions[i].level == Context_mission)
1285                         z++;
1286
1287         if (z) {
1288                 z = MessageBox("Deleting row will remove all missions on this row", "Notice", MB_ICONEXCLAMATION | MB_OKCANCEL);
1289                 if (z == IDCANCEL)
1290                         return;
1291         }
1292
1293         while (i--)
1294                 if (Campaign.missions[i].level == Context_mission)
1295                         remove_mission(i);
1296
1297         for (i=0; i<Campaign.num_missions; i++)
1298                 if (Campaign.missions[i].level > Context_mission)
1299                         Campaign.missions[i].level--;
1300
1301         total_levels--;
1302         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1303         Invalidate();
1304         UpdateWindow();
1305         Campaign_modified = 1;
1306 }
1307
1308 void campaign_tree_view::OnInsertRow() 
1309 {
1310         int i;
1311
1312         for (i=0; i<Campaign.num_missions; i++)
1313                 if (Campaign.missions[i].level >= Context_mission)
1314                         Campaign.missions[i].level++;
1315
1316         total_levels++;
1317         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1318         Invalidate();
1319         UpdateWindow();
1320         Campaign_modified = 1;
1321 }
1322
1323 void campaign_tree_view::OnAddRepeat() 
1324 {
1325         if (add_link(Context_mission, Context_mission)) {
1326                 MessageBox("Too many links exist.  Can't add any more.");
1327                 return;
1328         }
1329 }
1330
1331 void campaign_tree_view::OnEndOfCampaign() 
1332 {
1333         if ( add_link(Context_mission, -1) ) {
1334                 MessageBox("Too many links exist.  Cannot add any more.");
1335                 return;
1336         }
1337 }
1338