]> icculus.org git repositories - taylor/freespace2.git/blob - src/fred2/campaigntreeview.cpp
Initial revision
[taylor/freespace2.git] / src / fred2 / campaigntreeview.cpp
1 // CampaignTreeView.cpp : implementation file
2 //
3
4 #include "stdafx.h"
5 #include "fred.h"
6 #include "campaigntreeview.h"
7 #include "campaigneditordlg.h"
8 #include "campaigntreewnd.h"
9 #include "missionparse.h"
10
11 #ifdef _DEBUG
12 #define new DEBUG_NEW
13 #undef THIS_FILE
14 static char THIS_FILE[] = __FILE__;
15 #endif
16
17 LOCAL int Bx, By, Mission_dragging = -1, Mission_dropping = -1, Context_mission;
18 int Total_links = 0;
19 int CTV_button_down = 0;
20 int Level_counts[MAX_LEVELS];
21 int Sorted[MAX_CAMPAIGN_MISSIONS];
22 campaign_tree_element Elements[MAX_CAMPAIGN_MISSIONS];
23 campaign_tree_link Links[MAX_CAMPAIGN_TREE_LINKS];
24 LOCAL CRect Dragging_rect;
25 LOCAL CSize Rect_offset, Last_draw_size;
26
27 /////////////////////////////////////////////////////////////////////////////
28 // campaign_tree_view
29
30 IMPLEMENT_DYNCREATE(campaign_tree_view, CScrollView)
31
32 campaign_tree_view *Campaign_tree_viewp;
33
34 campaign_tree_view::campaign_tree_view()
35 {
36         total_levels = 1;
37         total_width = 1;
38 }
39
40 campaign_tree_view::~campaign_tree_view()
41 {
42 }
43
44 BEGIN_MESSAGE_MAP(campaign_tree_view, CScrollView)
45         //{{AFX_MSG_MAP(campaign_tree_view)
46         ON_WM_LBUTTONDOWN()
47         ON_WM_MOUSEMOVE()
48         ON_WM_LBUTTONUP()
49         ON_WM_CREATE()
50         ON_WM_CONTEXTMENU()
51         ON_COMMAND(ID_REMOVE_MISSION, OnRemoveMission)
52         ON_COMMAND(ID_DELETE_ROW, OnDeleteRow)
53         ON_COMMAND(ID_INSERT_ROW, OnInsertRow)
54         ON_COMMAND(ID_ADD_REPEAT, OnAddRepeat)
55         ON_COMMAND(ID_END_OF_CAMPAIGN, OnEndOfCampaign)
56         //}}AFX_MSG_MAP
57 END_MESSAGE_MAP()
58
59 /////////////////////////////////////////////////////////////////////////////
60 // campaign_tree_view drawing
61
62 #define LEVEL_HEIGHT            75
63 #define CELL_WIDTH              150
64 #define CELL_TEXT_WIDTH 130
65
66 int campaign_tree_view::OnCreate(LPCREATESTRUCT lpCreateStruct) 
67 {
68         if (CScrollView::OnCreate(lpCreateStruct) == -1)
69                 return -1;
70
71         return 0;
72 }
73
74 void campaign_tree_view::OnDraw(CDC* pDC)
75 {
76         char str[256];
77         int i, x, y, f, t;
78         BOOL r;
79         CSize size;
80         CRect rect;
81         CPen black_pen, white_pen, red_pen, blue_pen, green_pen;
82         CBrush gray_brush;
83         TEXTMETRIC tm;
84
85         // setup text drawing stuff
86         pDC->SetTextAlign(TA_TOP | TA_CENTER);
87         pDC->SetTextColor(RGB(0, 0, 0));
88         pDC->SetBkMode(TRANSPARENT);
89
90         // figure out text box sizes
91         r = pDC->GetTextMetrics(&tm);
92         Assert(r);
93         Bx = CELL_TEXT_WIDTH + 4;
94         By = tm.tmHeight + 4;
95
96         r = gray_brush.CreateSolidBrush(RGB(192, 192, 192));
97         Assert(r);
98         pDC->FillRect(CRect(0, 0, total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT), &gray_brush);
99
100         // create pens
101         r = black_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 0));
102         Assert(r);
103         r = white_pen.CreatePen(PS_SOLID, 1, RGB(255, 255, 255));
104         Assert(r);
105         r = red_pen.CreatePen(PS_SOLID, 1, RGB(192, 0, 0));
106         Assert(r);
107         r = blue_pen.CreatePen(PS_SOLID, 1, RGB(0, 0, 192));
108         Assert(r);
109         r = green_pen.CreatePen(PS_SOLID, 1, RGB(0, 192, 0));
110         Assert(r);
111
112         pDC->SelectObject(&black_pen);
113         // draw level seperators
114         for (i=1; i<total_levels; i++) {
115                 pDC->MoveTo(0, i * LEVEL_HEIGHT - 1);
116                 pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT - 1);
117         }
118
119         pDC->SelectObject(&white_pen);
120         for (i=1; i<total_levels; i++) {
121                 pDC->MoveTo(0, i * LEVEL_HEIGHT);
122                 pDC->LineTo(total_width * CELL_WIDTH, i * LEVEL_HEIGHT);
123         }
124
125
126         // draw edges of the whole tree rectangle
127         pDC->SelectObject(&black_pen);
128         pDC->MoveTo(0, total_levels * LEVEL_HEIGHT);
129         pDC->LineTo(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT);
130         pDC->LineTo(total_width * CELL_WIDTH, -1);
131
132         // draw text boxes and text
133
134         for (i=0; i<Campaign.num_missions; i++) {
135                 x = (Campaign.missions[i].pos + 1) * CELL_WIDTH / 2;
136                 y = Campaign.missions[i].level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2;
137                 Elements[i].box.left = x - Bx / 2;
138                 Elements[i].box.right = Elements[i].box.left + Bx;
139                 Elements[i].box.top = y - By / 2;
140                 Elements[i].box.bottom = Elements[i].box.top + By;
141   
142                 strcpy(str, Campaign.missions[i].name);
143                 str[strlen(str) - 4] = 0;  // strip extension from filename
144                 GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
145                 if (size.cx > CELL_TEXT_WIDTH) {
146                         strcpy(str + strlen(str) - 1, "...");
147                         GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
148                         while (size.cx > CELL_TEXT_WIDTH) {
149                                 strcpy(str + strlen(str) - 4, "...");
150                                 GetTextExtentPoint32(pDC->m_hDC, str, strlen(str), &size);
151                         }
152                 }
153
154                 if (i == Cur_campaign_mission) {
155                         pDC->SetTextColor(RGB(192, 0, 0));
156                         pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(0, 0, 0), RGB(255, 255, 255));
157
158                 } else {
159                         pDC->SetTextColor(RGB(0, 0, 0));
160                         pDC->Draw3dRect(x - Bx / 2, y - By / 2, Bx, By, RGB(255, 255, 255), RGB(0, 0, 0));
161                 }
162
163                 pDC->TextOut(x, y - By / 2 + 2, str, strlen(str));
164         }
165
166         for (i=0; i<Total_links; i++) {
167                 f = Links[i].from;
168                 t = Links[i].to;
169
170                 if (t == -1) {
171                         continue;
172                 }
173                 Links[i].p1.x = Elements[f].box.left + Links[i].from_pos * Bx / (Elements[f].from_links + 1);
174                 Links[i].p1.y = Elements[f].box.bottom;
175                 Links[i].p2.x = Elements[t].box.left + Links[i].to_pos * Bx / (Elements[t].to_links + 1);
176                 Links[i].p2.y = Elements[t].box.top;
177
178                 // if mission_loop link, select blue pen
179                 if (Links[i].mission_loop) {
180                         pDC->SelectObject(&blue_pen);
181                 }
182
183                 // if active link, select highlight pen (red - normal, green - mission loop)
184                 if (i == Cur_campaign_link) {
185                         if (Links[i].mission_loop) {
186                                 pDC->SelectObject(&green_pen);
187                         } else {
188                                 pDC->SelectObject(&red_pen);
189                         }
190                 }
191
192                 // draw a line between 'from' and 'to' mission.  to might be -1 in the case of end-campaign, so
193                 // don't draw line if so.
194                 if ( (f != t) && ( t != -1) ) {
195                         pDC->MoveTo(Links[i].p1);
196                         pDC->LineTo(Links[i].p2);
197                 }
198
199                 // select (defalt) black pen
200                 pDC->SelectObject(&black_pen);
201         }
202
203         pDC->SelectObject(&black_pen);
204 }
205
206 /////////////////////////////////////////////////////////////////////////////
207 // campaign_tree_view diagnostics
208
209 #ifdef _DEBUG
210 void campaign_tree_view::AssertValid() const
211 {
212         CScrollView::AssertValid();
213 }
214
215 void campaign_tree_view::Dump(CDumpContext& dc) const
216 {
217         CScrollView::Dump(dc);
218 }
219 #endif //_DEBUG
220
221 /////////////////////////////////////////////////////////////////////////////
222 // campaign_tree_view message handlers
223
224 void campaign_tree_view::OnInitialUpdate() 
225 {
226         CScrollView::OnInitialUpdate();
227         SetScrollSizes(MM_TEXT, CSize(320, 320));
228 }
229
230 void stuff_link_with_formula(int *link_idx, int formula, int mission_num)
231 {
232         int j, node, node2, node3;
233         if (formula >= 0) {
234                 if (!stricmp(CTEXT(formula), "cond")) {
235                         // sexp is valid
236
237                         node = CDR(formula);
238                         free_one_sexp(formula);
239                         while (node != -1) {
240                                 node2 = CAR(node);
241                                 Links[*link_idx].from = mission_num;
242                                 Links[*link_idx].sexp = CAR(node2);
243                                 Links[*link_idx].mission_loop_txt = NULL;
244                                 Links[*link_idx].mission_loop_brief_anim = NULL;
245                                 Links[*link_idx].mission_loop_brief_sound = NULL;
246                                 sexp_mark_persistent(CAR(node2));
247                                 free_one_sexp(node2);
248                                 node3 = CADR(node2);
249                                 if ( !stricmp( CTEXT(node3), "next-mission") ) {
250                                         node3 = CDR(node3);
251                                         for (j=0; j<Campaign.num_missions; j++)
252                                                 if (!stricmp(CTEXT(node3), Campaign.missions[j].name))
253                                                         break;
254
255                                         if (j < Campaign.num_missions) {  // mission is in campaign (you never know..)
256                                                 Links[(*link_idx)++].to = j;
257                                                 Elements[mission_num].from_links++;
258                                                 Elements[j].to_links++;
259                                         }
260
261                                 } else if ( !stricmp( CTEXT(node3), "end-of-campaign") ) {
262                                         Links[(*link_idx)++].to = -1;
263                                         Elements[mission_num].from_links++;
264
265                                 } else
266                                         Int3();                 // bogus operator in campaign file
267
268                                 free_sexp(CDR(node2));
269                                 free_one_sexp(node);
270                                 node = CDR(node);
271                         }
272                 }
273         }
274 }
275
276 // this function should only be called right after a campaign is loaded.  Calling it a second
277 // time without having loaded a campaign again will result in undefined behavior.
278 void campaign_tree_view::construct_tree()
279 {
280         int i;
281         free_links();
282
283         // initialize mission link counts
284         for (i=0; i<Campaign.num_missions; i++) {
285                 Elements[i].from_links = Elements[i].to_links = 0;
286         }
287
288         // analyze branching sexps and build links from them.
289         int link_idx = 0;
290         for (i=0; i<Campaign.num_missions; i++) {
291
292                 // do main campaign path
293                 stuff_link_with_formula(&link_idx, Campaign.missions[i].formula, i);
294
295                 // do mission loop path
296                 if ( Campaign.missions[i].has_mission_loop ) {
297                         stuff_link_with_formula(&link_idx, Campaign.missions[i].mission_loop_formula, i);
298                         Links[link_idx-1].mission_loop_txt = Campaign.missions[i].mission_loop_desc;
299                         Links[link_idx-1].mission_loop_brief_anim = Campaign.missions[i].mission_loop_brief_anim;
300                         Links[link_idx-1].mission_loop_brief_sound = Campaign.missions[i].mission_loop_brief_sound;
301                         Links[link_idx-1].mission_loop = 1;
302                 }
303         }
304
305         for (i=0; i<Campaign.num_missions; i++) {
306                 Sorted[i] = i;
307         }
308
309         Total_links = link_idx;
310         if (Campaign.realign_required) {
311                 realign_tree();
312                 Campaign.realign_required = 0;
313         }
314 }
315
316 void campaign_tree_view::initialize()
317 {
318         int i, z;
319
320         total_levels = total_width = 1;
321         for (i=0; i<MAX_LEVELS; i++)
322                 Level_counts[i] = 0;
323
324         for (i=0; i<Campaign.num_missions; i++) {
325                 z = Campaign.missions[i].level;
326                 if (z + 2 > total_levels)
327                         total_levels = z + 2;
328
329                 Level_counts[z]++;
330                 z = (Campaign.missions[i].pos + 3) / 2;
331                 if (z > total_width)
332                         total_width = z;
333         }
334
335         sort_links();
336         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
337         Invalidate();
338 }
339
340 void campaign_tree_view::free_links()
341 {
342         int i;
343
344         for (i=0; i<Total_links; i++) {
345                 sexp_unmark_persistent(Links[i].sexp);
346                 free_sexp2(Links[i].sexp);
347         }
348
349         Total_links = 0;
350 }
351
352 void campaign_tree_view::realign_tree()
353 {
354         int i, j, z, offset, level, pos;
355
356         // figure out what level each mission lies on and an initial position on that level
357         level = pos = total_width = 0;
358         for (i=0; i<Campaign.num_missions; i++) {
359                 z = Sorted[i];
360                 for (j=0; j<Total_links; j++)
361                         if (Links[j].to == z) {
362                                 Assert(Campaign.missions[Links[j].from].level <= Campaign.missions[z].level);  // links can't go up the tree, only down
363                                 if (Campaign.missions[Links[j].from].level == level) {
364                                         level++;  // force to next level in tree
365                                         pos = 0;
366                                         break;
367                                 }
368                         }
369
370                 Campaign.missions[z].level = level;
371                 Campaign.missions[z].pos = pos++;
372                 if (pos > total_width)
373                         total_width = pos;
374
375                 Level_counts[level] = pos;
376                 if (!z) {  // topmost node must always be alone on level
377                         level++;
378                         pos = 0;
379                 }
380         }
381
382         // now calculate the true x position of each mission
383         for (i=0; i<Campaign.num_missions; i++) {
384                 offset = total_width - Level_counts[Campaign.missions[i].level];
385                 Campaign.missions[i].pos = Campaign.missions[i].pos * 2 + offset;
386         }
387 }
388
389 void campaign_tree_view::sort_links()
390 {
391         int i, j, k, z, to_count, from_count, swap;
392         int to_list[MAX_CAMPAIGN_TREE_LINKS];
393         int from_list[MAX_CAMPAIGN_TREE_LINKS];
394
395         for (i=0; i<Campaign.num_missions; i++) {
396                 // build list of all to and from links for one mission at a time
397                 to_count = from_count = 0;
398                 for (j=0; j<Total_links; j++) {
399                         if ((Links[j].to == i) && (Links[j].from == i))
400                                 continue;  // ignore 'repeat mission' links
401                         if (Links[j].to == i)
402                                 to_list[to_count++] = j;
403                         if (Links[j].from == i)
404                                 from_list[from_count++] = j;
405                 }
406
407                 // sort to links, left to right and top to bottom
408                 for (j=0; j<to_count-1; j++)
409                         for (k=j+1; k<to_count; k++) {
410                                 swap = 0;
411                                 z = Campaign.missions[Links[to_list[j]].from].pos -
412                                         Campaign.missions[Links[to_list[k]].from].pos;
413
414                                 if (z > 0)  // sort left to right
415                                         swap = 1;
416
417                                 else if (!z) {  // both have same position?
418                                         z = Campaign.missions[Links[to_list[j]].from].level -
419                                                 Campaign.missions[Links[to_list[k]].from].level;
420
421                                         // see where from link position is relative to to link position
422                                         if (Campaign.missions[i].pos < Campaign.missions[Links[to_list[j]].from].pos) {
423                                                 if (z > 0)  // sort bottom to top
424                                                         swap = 1;
425
426                                         } else {
427                                                 if (z < 0) // sort top to bottom
428                                                         swap = 1;
429                                         }
430                                 }
431
432                                 if (swap) {
433                                         z = to_list[j];
434                                         to_list[j] = to_list[k];
435                                         to_list[k] = z;
436                                 }
437                         }
438
439                 // set all links to positions
440                 for (j=0; j<to_count; j++)
441                         Links[to_list[j]].to_pos = j + 1;
442
443                 // sort from links, left to right and bottom to top
444                 for (j=0; j<from_count-1; j++)
445                         for (k=j+1; k<from_count; k++) {
446                                 swap = 0;
447                                 z = Campaign.missions[Links[from_list[j]].to].pos -
448                                         Campaign.missions[Links[from_list[k]].to].pos;
449
450                                 if (z > 0)
451                                         swap = 1;
452                                         
453                                 else if (!z) {
454                                         z = Campaign.missions[Links[from_list[j]].to].level -
455                                                 Campaign.missions[Links[from_list[k]].to].level;
456
457                                         if (Campaign.missions[i].pos < Campaign.missions[Links[from_list[j]].to].pos) {
458                                                 if (z < 0)
459                                                         swap = 1;
460
461                                         } else {
462                                                 if (z > 0)
463                                                         swap = 1;
464                                         }
465                                 }
466
467                                 if (swap) {
468                                         z = from_list[j];
469                                         from_list[j] = from_list[k];
470                                         from_list[k] = z;
471                                 }
472                         }
473
474                 // set all links from positions
475                 for (j=0; j<from_count; j++)
476                         Links[from_list[j]].from_pos = j + 1;
477         }
478 }
479
480 void campaign_tree_view::OnLButtonDown(UINT nFlags, CPoint point) 
481 {
482         int i;
483         CString str;
484         CEdit *box;
485         CListBox *listbox;
486         CClientDC dc(this);
487
488         OnPrepareDC(&dc);
489         dc.DPtoLP(&point);
490         if (nFlags & MK_CONTROL) {
491                 listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
492                 i = listbox->GetCurSel();
493
494                 Mission_dropping = -1;
495                 if (i != LB_ERR) {
496                         Mission_dropping = i;
497                         SetCapture();
498                 }
499
500                 Last_draw_size = CSize(0, 0);
501                 Dragging_rect.SetRect(0, 0, 1, 1);
502                 dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
503
504         } else {
505                 if ( (Cur_campaign_link >= 0) && Links[Cur_campaign_link].mission_loop) {
506                         // HACK!!  UPDATE mission loop desc before changing selections
507                         // save mission loop desc
508                         char buffer[MISSION_DESC_LENGTH];
509                         box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_MISSISON_LOOP_DESC);
510                         box->GetWindowText(buffer, MISSION_DESC_LENGTH);
511                         if (strlen(buffer)) {
512                                 if (Links[Cur_campaign_link].mission_loop_txt) {
513                                         free(Links[Cur_campaign_link].mission_loop_txt);
514                                 }
515                                 Links[Cur_campaign_link].mission_loop_txt = strdup(buffer);
516                         } else {
517                                 Links[Cur_campaign_link].mission_loop_txt = NULL;
518                         }
519
520                         // HACK!!  UPDATE mission loop desc before changing selections
521                         // save mission loop desc                       
522                         box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_ANIM);
523                         box->GetWindowText(buffer, MISSION_DESC_LENGTH);
524                         if (strlen(buffer)) {
525                                 if (Links[Cur_campaign_link].mission_loop_brief_anim) {
526                                         free(Links[Cur_campaign_link].mission_loop_brief_anim);
527                                 }
528                                 Links[Cur_campaign_link].mission_loop_brief_anim = strdup(buffer);
529                         } else {
530                                 Links[Cur_campaign_link].mission_loop_brief_anim = NULL;
531                         }
532
533                         // HACK!!  UPDATE mission loop desc before changing selections
534                         // save mission loop desc                       
535                         box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_LOOP_BRIEF_SOUND);
536                         box->GetWindowText(buffer, MISSION_DESC_LENGTH);
537                         if (strlen(buffer)) {
538                                 if (Links[Cur_campaign_link].mission_loop_brief_sound) {
539                                         free(Links[Cur_campaign_link].mission_loop_brief_sound);
540                                 }
541                                 Links[Cur_campaign_link].mission_loop_brief_sound = strdup(buffer);
542                         } else {
543                                 Links[Cur_campaign_link].mission_loop_brief_sound = NULL;
544                         }
545                 }
546                 Mission_dragging = Cur_campaign_mission = Cur_campaign_link = -1;
547                 for (i=0; i<Campaign.num_missions; i++)
548                         if (Elements[i].box.PtInRect(point)) {
549                                 SetCapture();
550
551                                 Mission_dragging = Cur_campaign_mission = i;
552                                 Dragging_rect = Elements[i].box;
553                                 Rect_offset = Dragging_rect.TopLeft() - point;
554                                 Last_draw_size = CSize(4, 4);
555                                 if (Campaign.missions[Cur_campaign_mission].num_goals < 0)  // haven't loaded goal names yet (or notes)
556                                         read_mission_goal_list(Cur_campaign_mission);
557
558                                 if (Campaign.missions[Cur_campaign_mission].notes) {
559                                         str = convert_multiline_string(Campaign.missions[Cur_campaign_mission].notes);
560                                         box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
561                                         if (box)
562                                                 box->SetWindowText(str);
563                                 }
564
565                                 Campaign_tree_formp->mission_selected(Cur_campaign_mission);
566                                 break;
567                         }
568         }
569         
570         Invalidate();
571         UpdateWindow();
572         Campaign_tree_formp->load_tree();
573         if (Mission_dragging != -1) {
574                 CRect rect = Dragging_rect;
575
576                 dc.LPtoDP(&rect);
577                 dc.DrawDragRect(rect, Last_draw_size, NULL, CSize(0, 0));
578         }
579
580         CScrollView::OnLButtonDown(nFlags, point);
581 }
582
583 void campaign_tree_view::OnMouseMove(UINT nFlags, CPoint point)
584 {
585         int i, level, pos, x, y;
586         CSize draw_size;
587         CRect rect, r1;
588         CClientDC dc(this);
589
590         OnPrepareDC(&dc);
591         dc.DPtoLP(&point);
592         if ((Mission_dragging >= 0) || (Mission_dropping >= 0)) {
593                 if (GetCapture() != this) {
594                         rect = Dragging_rect;
595                         dc.LPtoDP(&rect);
596                         dc.DrawDragRect(rect, CSize(0, 0), rect, Last_draw_size);
597                         Mission_dragging = Mission_dropping = -1;
598
599                 } else {
600                         for (i=0; i<Campaign.num_missions; i++)
601                                 if (Elements[i].box.PtInRect(point))
602                                         break;
603
604                         if ((i < Campaign.num_missions) && (Mission_dropping < 0)) {  // on a mission box?
605                                 draw_size = CSize(4, 4);
606                                 rect = Elements[i].box;
607
608                         } else {
609                                 level = query_level(point);
610                                 pos = query_pos(point);
611                                 if ((level < 0) || (pos < 0)) {  // off table?
612                                         draw_size = CSize(0, 0);
613                                         rect = Dragging_rect;
614
615                                 } else {
616                                         draw_size = CSize(2, 2);
617                                         for (i=0; i<Campaign.num_missions; i++)
618                                                 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
619                                                         pos = query_alternate_pos(point);
620                                                         break;
621                                                 }
622
623                                         x = pos * CELL_WIDTH / 2 - Bx / 2;
624                                         y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - By / 2;
625                                         rect.SetRect(x, y, x + Bx, y + By);
626                                 }
627                         }
628
629                         r1 = rect;
630                         dc.LPtoDP(&r1);
631                         dc.LPtoDP(&Dragging_rect);
632                         dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
633                         Dragging_rect = rect;
634                         Last_draw_size = draw_size;
635                 }
636         }
637
638         CScrollView::OnMouseMove(nFlags, point);
639 }
640
641 void campaign_tree_view::OnLButtonUp(UINT nFlags, CPoint point) 
642 {
643         int i, j, z, level, pos;
644         CClientDC dc(this);
645
646         OnPrepareDC(&dc);
647         dc.DPtoLP(&point);
648         if (Mission_dropping >= 0) {  // dropping a new mission into campaign?
649                 z = Mission_dropping;
650                 Mission_dropping = -1;
651                 if (GetCapture() == this) {
652                         ReleaseCapture();
653                         dc.LPtoDP(&Dragging_rect);
654                         dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
655
656                         drop_mission(z, point);
657                         return;
658                 }
659
660         } else if (Mission_dragging >= 0) {  // moving position of a mission?
661                 z = Mission_dragging;
662                 Mission_dragging = -1;
663                 if (GetCapture() == this) {
664                         ReleaseCapture();
665                         dc.LPtoDP(&Dragging_rect);
666                         dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
667                         for (i=0; i<Campaign.num_missions; i++)
668                                 if (Elements[i].box.PtInRect(point)) {  // see if released on another mission
669                                         if (i == z)  // released on the same mission?
670                                                 return;
671
672                                         for (j=0; j<Total_links; j++)
673                                                 if ((Links[j].from == z) && (Links[j].to == i))
674                                                         return;  // already linked
675
676                                         if (Total_links >= MAX_CAMPAIGN_TREE_LINKS) {
677                                                 MessageBox("Too many links exist.  Can't add any more.");
678                                                 return;
679                                         }
680
681                                         if (Campaign.missions[z].level >= Campaign.missions[i].level) {
682                                                 MessageBox("A branch can only be set to a mission on a lower level");
683                                                 return;
684                                         }
685
686                                         add_link(z, i);
687                                         return;
688                                 }
689
690                         // at this point, we are dragging a mission to a new place
691                         level = query_level(point);
692                         pos = query_pos(point);
693                         if ((level < 0) || (pos < 0))
694                                 return;
695
696                         if (!level && (get_root_mission() >= 0)) {
697                                 MessageBox("Can't move mission to this level.  There is already a top level mission");
698                                 return;
699                         }
700
701                         for (i=0; i<Campaign.num_missions; i++)
702                                 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
703                                         pos = query_alternate_pos(point);
704                                         break;
705                                 }
706
707                         for (i=0; i<Total_links; i++)
708                                 if (Links[i].to == z)
709                                         if (level <= Campaign.missions[Links[i].from].level) {
710                                                 MessageBox("Can't move mission to that level, as it would be\n"
711                                                         "above a parent mission", "Error");
712
713                                                 return;
714                                         }
715
716                         Campaign.missions[z].level = level;
717                         Campaign.missions[z].pos = pos - 1;
718                         correct_position(z);
719                         sort_links();
720                         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
721                         Invalidate();
722                         Campaign_modified = 1;
723                         return;
724                 }
725         }
726         
727         CScrollView::OnLButtonUp(nFlags, point);
728 }
729
730 int campaign_tree_view::add_link(int from, int to)
731 {
732         if (Total_links >= MAX_CAMPAIGN_TREE_LINKS)
733                 return -1;
734
735         Campaign_tree_formp->load_tree(1);
736         Links[Total_links].from = from;
737         Links[Total_links].to = to;
738         Links[Total_links].sexp = Locked_sexp_true;
739         Links[Total_links].mission_loop = false;
740         Links[Total_links].mission_loop_txt = NULL;
741         Links[Total_links].mission_loop_brief_anim = NULL;
742         Links[Total_links].mission_loop_brief_sound = NULL;
743         Total_links++;
744         if (from != to) {
745                 Elements[from].from_links++;
746                 Elements[to].to_links++;
747         }
748
749         sort_links();
750         Campaign_tree_formp->load_tree(0);
751         Invalidate();
752         Campaign_modified = 1;
753         return 0;
754 }
755
756 int campaign_tree_view::query_level(const CPoint& p)
757 {
758         int level;
759
760         if ((p.y < 0) || (p.y >= total_levels * LEVEL_HEIGHT))
761                 return -1;
762
763         level = p.y / LEVEL_HEIGHT;
764         Assert((level >= 0) && (level < total_levels));
765         return level;
766 }
767
768 int campaign_tree_view::query_pos(const CPoint& p)
769 {
770         int pos;
771         
772         if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
773                 return -1;
774
775         pos = ((p.x * 4 / CELL_WIDTH) + 1) / 2;
776         Assert((pos >= 0) && (pos <= total_width * 2));
777         return pos;
778 }
779
780 int campaign_tree_view::query_alternate_pos(const CPoint& p)
781 {
782         int x, pos;
783         
784         if ((p.x < 0) || (p.x >= total_width * CELL_WIDTH))
785                 return -1;
786
787         x = p.x * 4 / CELL_WIDTH;
788         pos = (x + 1) / 2;
789         if (x & 1) // odd number
790                 pos--;
791         else  // even number
792                 pos++;
793
794         Assert((pos >= 0) && (pos <= total_width * 2));
795         return pos;
796 }
797 /*
798 DROPEFFECT campaign_tree_view::OnDragEnter(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point)
799 {
800         CClientDC dc(this);
801         OnPrepareDC(&dc);
802
803         if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
804                 return DROPEFFECT_NONE;  // data isn't a mission filename, the only valid data to drop here
805
806         Mission_dropping = 1;
807         Last_draw_size = CSize(0, 0);
808         Dragging_rect.SetRect(0, 0, 1, 1);
809         dc.DrawDragRect(Dragging_rect, Last_draw_size, NULL, CSize(0, 0));
810         return DROPEFFECT_MOVE;
811 }
812
813 void campaign_tree_view::OnDragLeave()
814 {
815         CScrollView::OnDragLeave();
816         if (Mission_dropping >= 0) {
817                 CClientDC dc(this);
818                 OnPrepareDC(&dc);
819
820                 dc.LPtoDP(&Dragging_rect);
821                 dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
822                 Mission_dropping = -1;
823         }
824 }
825
826 DROPEFFECT campaign_tree_view::OnDragOver(COleDataObject* pDataObject, DWORD dwKeyState, CPoint point) 
827 {
828         int i, level, pos, x, y;
829         CSize draw_size;
830         CRect rect, r1;
831         DROPEFFECT r = DROPEFFECT_MOVE;
832
833         if (Mission_dropping < 0)
834                 return DROPEFFECT_NONE;
835
836         CClientDC dc(this);
837         OnPrepareDC(&dc);
838         dc.DPtoLP(&point);
839
840         level = query_level(point);
841         pos = query_pos(point);
842         if ((level < 0) || (pos < 0)) {  // off table?
843                 draw_size = CSize(0, 0);
844                 rect = Dragging_rect;
845                 r = DROPEFFECT_NONE;
846
847         } else {
848                 draw_size = CSize(2, 2);
849                 for (i=0; i<Campaign.num_missions; i++)
850                         if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
851                                 pos = query_alternate_pos(point);
852                                 break;
853                         }
854
855                 x = pos * CELL_WIDTH / 2 - Dragging_rect.Width() / 2;
856                 y = level * LEVEL_HEIGHT + LEVEL_HEIGHT / 2 - Dragging_rect.Height() / 2;
857                 rect.SetRect(x, y, x + Bx, y + By);
858         }
859
860         r1 = rect;
861         dc.LPtoDP(&r1);
862         dc.LPtoDP(&Dragging_rect);
863         dc.DrawDragRect(r1, draw_size, Dragging_rect, Last_draw_size);
864         Dragging_rect = rect;
865         Last_draw_size = draw_size;
866
867         return r;
868 }
869
870 BOOL campaign_tree_view::OnDrop(COleDataObject* pDataObject, DROPEFFECT dropEffect, CPoint point) 
871 {
872         int i, level, pos;
873         cmission *cm;
874         HGLOBAL hGlobal;
875         LPCSTR pData;
876         mission a_mission;
877
878         if (Mission_dropping < 0)
879                 return FALSE;
880
881         // If the dropEffect requested is not a MOVE, return FALSE to 
882         // signal no drop. (No COPY into trashcan)
883         if ((dropEffect & DROPEFFECT_MOVE) != DROPEFFECT_MOVE)
884                 return FALSE;
885
886         CClientDC dc(this);
887         OnPrepareDC(&dc);
888         dc.DPtoLP(&point);
889
890         dc.LPtoDP(&Dragging_rect);
891         dc.DrawDragRect(Dragging_rect, CSize(0, 0), Dragging_rect, Last_draw_size);
892         Mission_dropping = -1;
893
894         if (!pDataObject->IsDataAvailable((unsigned short)Mission_filename_cb_format))
895                 return FALSE;  // data isn't a mission filename, the only valid data to drop here
896
897         // Get text data from COleDataObject
898         hGlobal = pDataObject->GetGlobalData((unsigned short)Mission_filename_cb_format);
899
900         // Get pointer to data
901         pData = (LPCSTR) GlobalLock(hGlobal);
902         ASSERT(pData);
903
904         if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) {  // Can't add any more
905                 GlobalUnlock(hGlobal);
906                 MessageBox("Too many missions.  Can't add more to Campaign.", "Error");
907                 return FALSE;
908         }
909
910         level = query_level(point);
911         pos = query_pos(point);
912         Assert((level >= 0) && (pos >= 0));  // this should be impossible
913         if (!level && (get_root_mission() >= 0)) {
914                 GlobalUnlock(hGlobal);
915                 MessageBox("Only 1 mission may be in the top level");
916                 return FALSE;
917         }
918
919         // check the number of players in a multiplayer campaign against the number of players
920         // in the mission that was just dropped
921         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
922                 get_mission_info((char *)pData, &a_mission);
923                 if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
924                         char buf[256];
925
926                         sprintf( buf, "Mission \"%s\" is not a multiplayer mission", pData );
927                         MessageBox(buf, "Error");
928                         GlobalUnlock(hGlobal);
929                         return FALSE;
930                 }
931
932                 if ( Campaign.num_players != 0 ) {
933                         if ( Campaign.num_players != a_mission.num_players ) {
934                                 char buf[512];
935
936                                 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 );
937                                 MessageBox(buf, "Warning");
938                         }
939                 } else {
940                         Campaign.num_players = a_mission.num_players;
941                 }
942         }
943
944         Elements[Campaign.num_missions].from_links = Elements[Campaign.num_missions].to_links = 0;
945         cm = &(Campaign.missions[Campaign.num_missions++]);
946         cm->name = strdup(pData);
947         cm->formula = Locked_sexp_true;
948         cm->num_goals = -1;
949         cm->notes = NULL;
950         cm->briefing_cutscene[0] = 0;
951         for (i=0; i<Campaign.num_missions - 1; i++)
952                 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
953                         pos = query_alternate_pos(point);
954                         break;
955                 }
956
957         cm->level = level;
958         cm->pos = pos - 1;
959         correct_position(Campaign.num_missions - 1);
960         sort_links();
961         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
962         Invalidate();
963
964         // update and reinitialize dialog items
965         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
966                 Campaign_tree_formp->update();
967                 Campaign_tree_formp->initialize(0);
968         }
969
970         // Unlock memory - Send dropped text into the "bit-bucket"
971         GlobalUnlock(hGlobal);
972         Campaign_modified = 1;
973         return TRUE;
974 }
975 */
976
977 void campaign_tree_view::drop_mission(int m, CPoint point)
978 {
979         char name[MAX_FILENAME_LEN + 1];
980         int i, item, level, pos;
981         cmission *cm;
982         mission a_mission;
983         CListBox *listbox;
984
985         level = query_level(point);
986         pos = query_pos(point);
987         Assert((level >= 0) && (pos >= 0));  // this should be impossible
988
989         listbox = (CListBox *) &Campaign_tree_formp->m_filelist;
990         item = listbox->GetCurSel();
991         if (item == LB_ERR) {
992                 MessageBox("Select a mission from listbox to add.", "Error");
993                 return;
994         }
995
996         if (listbox->GetTextLen(item) > MAX_FILENAME_LEN) {
997                 char buf[256];
998
999                 sprintf(buf, "Filename is too long.  Must be %d or less characters.", MAX_FILENAME_LEN);
1000                 MessageBox(buf, "Error");
1001                 return;  // filename is too long.  Would overflow our buffer
1002         }
1003
1004         // grab the filename selected from the listbox
1005         listbox->GetText(item, name);
1006
1007         if (Campaign.num_missions >= MAX_CAMPAIGN_MISSIONS) {  // Can't add any more
1008                 MessageBox("Too many missions.  Can't add more to Campaign.", "Error");
1009                 return;
1010         }
1011
1012         if (!level && (get_root_mission() >= 0)) {
1013                 MessageBox("Only 1 mission may be in the top level");
1014                 return;
1015         }
1016
1017         // check the number of players in a multiplayer campaign against the number of players
1018         // in the mission that was just dropped
1019         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1020                 get_mission_info(name, &a_mission);
1021                 if ( !(a_mission.game_type & MISSION_TYPE_MULTI) ) {
1022                         char buf[256];
1023
1024                         sprintf(buf, "Mission \"%s\" is not a multiplayer mission", name);
1025                         MessageBox(buf, "Error");
1026                         return;
1027                 }
1028
1029                 if (Campaign.num_players != 0) {
1030                         if (Campaign.num_players != a_mission.num_players) {
1031                                 char buf[512];
1032
1033                                 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 );
1034                                 MessageBox(buf, "Warning");
1035                         }
1036
1037                 } else {
1038                         Campaign.num_players = a_mission.num_players;
1039                 }
1040         }
1041
1042         Elements[Campaign.num_missions].from_links = Elements[Campaign.num_missions].to_links = 0;
1043         cm = &(Campaign.missions[Campaign.num_missions++]);
1044         cm->name = strdup(name);
1045         cm->formula = Locked_sexp_true;
1046         cm->num_goals = -1;
1047         cm->notes = NULL;
1048         cm->briefing_cutscene[0] = 0;
1049         for (i=0; i<Campaign.num_missions - 1; i++)
1050                 if ((Campaign.missions[i].level == level) && (Campaign.missions[i].pos + 1 == pos)) {
1051                         pos = query_alternate_pos(point);
1052                         break;
1053                 }
1054
1055         cm->level = level;
1056         cm->pos = pos - 1;
1057         correct_position(Campaign.num_missions - 1);
1058         sort_links();
1059         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1060         Invalidate();
1061
1062         // update and reinitialize dialog items
1063         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1064                 Campaign_tree_formp->update();
1065                 Campaign_tree_formp->initialize(0);
1066         }
1067
1068         listbox->DeleteString(item);
1069         Campaign_modified = 1;
1070         return;
1071 }
1072
1073 void campaign_tree_view::sort_elements()
1074 {
1075         int i, j, s1, s2;
1076
1077         for (i=0; i<Campaign.num_missions; i++)
1078                 Sorted[i] = i;
1079
1080         // sort the tree, so realignment will work property
1081         for (i=1; i<Campaign.num_missions; i++) {
1082                 s1 = Sorted[i];
1083                 for (j=i-1; j>=0; j--) {
1084                         s2 = Sorted[j];
1085                         if ((Campaign.missions[s1].level > Campaign.missions[s2].level) ||
1086                                 ((Campaign.missions[s1].level == Campaign.missions[s2].level) &&
1087                                 (Campaign.missions[s1].pos > Campaign.missions[s2].pos)))
1088                                         break;
1089
1090                         Sorted[j + 1] = s2;
1091                 }
1092
1093                 Sorted[j + 1] = s1;
1094         }
1095 }
1096
1097 void campaign_tree_view::correct_position(int num)
1098 {
1099         int i, z;
1100
1101         // move missions down if required
1102         if (Campaign.missions[num].level + 2 > total_levels)
1103                 total_levels = Campaign.missions[num].level + 2;
1104
1105         for (i=0; i<Total_links; i++)
1106                 if (Links[i].from == num) {
1107                         z = Links[i].to;
1108                         if ( (num != z) && (Campaign.missions[num].level >= Campaign.missions[z].level) ) {
1109                                 Campaign.missions[z].level = Campaign.missions[num].level + 1;
1110                                 correct_position(z);
1111                         }
1112                 }
1113
1114         // space out horizontally to avoid overlap of missions
1115         horizontally_align_mission(num, -1);
1116         horizontally_align_mission(num, 1);
1117 }
1118
1119 void campaign_tree_view::horizontally_align_mission(int num, int dir)
1120 {
1121         int i, z;
1122
1123         if ((Campaign.missions[num].pos == -1) || (Campaign.missions[num].pos + 1 == total_width * 2)) {  // need to expand total_width
1124                 for (i=0; i<Campaign.num_missions; i++)
1125                         Campaign.missions[i].pos++;
1126
1127                 total_width++;
1128         }
1129
1130         for (i=0; i<Campaign.num_missions; i++) {
1131                 if (i == num)
1132                         continue;
1133
1134                 if (Campaign.missions[i].level == Campaign.missions[num].level) {
1135                         z = Campaign.missions[i].pos - Campaign.missions[num].pos;
1136                         if (dir < 0) {
1137                                 if (!z || (z == -1)) {
1138                                         Campaign.missions[i].pos = Campaign.missions[num].pos - 2;
1139                                         horizontally_align_mission(i, -1);
1140                                 }
1141
1142                         } else {
1143                                 if (!z || (z == 1)) {
1144                                         Campaign.missions[i].pos = Campaign.missions[num].pos + 2;
1145                                         horizontally_align_mission(i, 1);
1146                                 }
1147                         }
1148                 }
1149         }
1150 }
1151
1152 void campaign_tree_view::delete_link(int num)
1153 {
1154         Assert((num >= 0) && (num < Total_links));
1155         if (Links[num].from != Links[num].to) {
1156                 Elements[Links[num].from].from_links--;
1157                 Elements[Links[num].to].to_links--;
1158         }
1159
1160         sexp_unmark_persistent(Links[num].sexp);
1161         free_sexp2(Links[num].sexp);
1162         while (num < Total_links - 1) {
1163                 Links[num] = Links[num + 1];
1164                 num++;
1165         }
1166
1167         Total_links--;
1168         sort_links();
1169         Invalidate();
1170         Campaign_modified = 1;
1171         return;
1172 }
1173
1174 int campaign_tree_view::get_root_mission()
1175 {
1176         int i;
1177
1178         for (i=0; i<Campaign.num_missions; i++)
1179                 if (!Campaign.missions[i].level)
1180                         return i;
1181
1182         return -1;
1183 }
1184
1185 void campaign_tree_view::OnContextMenu(CWnd* pWnd, CPoint point) 
1186 {
1187         int i;
1188         CMenu menu, *popup;
1189         CPoint p = point;
1190         CClientDC dc(this);
1191
1192         OnPrepareDC(&dc);
1193         dc.DPtoLP(&p);
1194
1195         ScreenToClient(&p);
1196         for (i=0; i<Campaign.num_missions; i++)
1197                 if (Elements[i].box.PtInRect(p))
1198                         break;
1199
1200         if (i < Campaign.num_missions) {  // clicked on a mission
1201                 Context_mission = i;
1202                 if (menu.LoadMenu(IDR_CPGN_VIEW_ON)) {
1203                         popup = menu.GetSubMenu(0);
1204                         ASSERT(popup);
1205                         popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1206                 }
1207
1208         } else {
1209                 Context_mission = query_level(p);
1210                 if ((Context_mission >= 0) && (Context_mission < total_levels))
1211                         if (menu.LoadMenu(IDR_CPGN_VIEW_OFF)) {
1212                                 popup = menu.GetSubMenu(0);
1213                                 ASSERT(popup);
1214                                 popup->TrackPopupMenu(TPM_LEFTALIGN | TPM_RIGHTBUTTON, point.x, point.y, this);
1215                         }
1216         }
1217 }
1218
1219 void campaign_tree_view::OnRemoveMission() 
1220 {
1221         remove_mission(Context_mission);
1222         Invalidate();
1223         UpdateWindow();
1224
1225         // for multiplayer missions, update the data and reiniailize the dialog -- the number of player
1226         // in the mission might have changed because of deletion of the first mission
1227         if ( Campaign.type != CAMPAIGN_TYPE_SINGLE ) {
1228                 Campaign_tree_formp->update();
1229                 Campaign_tree_formp->initialize();
1230         }
1231 }
1232
1233 void campaign_tree_view::remove_mission(int m)
1234 {
1235         int i, z;
1236         CEdit *box;
1237
1238         Assert(m >= 0);
1239         Campaign_tree_formp->m_filelist.AddString(Campaign.missions[m].name);
1240
1241         z = --Campaign.num_missions;
1242         i = Total_links;
1243         while (i--) {
1244                 if ((Links[i].from == m) || (Links[i].to == m))
1245                         delete_link(i);
1246                 if (Links[i].from == z)
1247                         Links[i].from = m;
1248                 if (Links[i].to == z)
1249                         Links[i].to = m;
1250         }
1251
1252         Elements[m] = Elements[z];
1253         Campaign.missions[m] = Campaign.missions[z];
1254         if (m == Cur_campaign_mission) {
1255                 Cur_campaign_mission = -1;
1256                 box = (CEdit *) Campaign_tree_formp->GetDlgItem(IDC_HELP_BOX);
1257                 if (box)
1258                         box->SetWindowText("");
1259
1260                 Campaign_tree_formp->load_tree(0);
1261         }
1262
1263         Campaign_modified = 1;
1264 }
1265
1266 void campaign_tree_view::OnDeleteRow() 
1267 {
1268         int i, z;
1269
1270         if (!Context_mission) {
1271                 MessageBox("Can't delete the top level");
1272                 return;
1273         }
1274
1275         for (i=z=0; i<Campaign.num_missions; i++)
1276                 if (Campaign.missions[i].level == Context_mission)
1277                         z++;
1278
1279         if (z) {
1280                 z = MessageBox("Deleting row will remove all missions on this row", "Notice", MB_ICONEXCLAMATION | MB_OKCANCEL);
1281                 if (z == IDCANCEL)
1282                         return;
1283         }
1284
1285         while (i--)
1286                 if (Campaign.missions[i].level == Context_mission)
1287                         remove_mission(i);
1288
1289         for (i=0; i<Campaign.num_missions; i++)
1290                 if (Campaign.missions[i].level > Context_mission)
1291                         Campaign.missions[i].level--;
1292
1293         total_levels--;
1294         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1295         Invalidate();
1296         UpdateWindow();
1297         Campaign_modified = 1;
1298 }
1299
1300 void campaign_tree_view::OnInsertRow() 
1301 {
1302         int i;
1303
1304         for (i=0; i<Campaign.num_missions; i++)
1305                 if (Campaign.missions[i].level >= Context_mission)
1306                         Campaign.missions[i].level++;
1307
1308         total_levels++;
1309         SetScrollSizes(MM_TEXT, CSize(total_width * CELL_WIDTH, total_levels * LEVEL_HEIGHT));
1310         Invalidate();
1311         UpdateWindow();
1312         Campaign_modified = 1;
1313 }
1314
1315 void campaign_tree_view::OnAddRepeat() 
1316 {
1317         if (add_link(Context_mission, Context_mission)) {
1318                 MessageBox("Too many links exist.  Can't add any more.");
1319                 return;
1320         }
1321 }
1322
1323 void campaign_tree_view::OnEndOfCampaign() 
1324 {
1325         if ( add_link(Context_mission, -1) ) {
1326                 MessageBox("Too many links exist.  Cannot add any more.");
1327                 return;
1328         }
1329 }