# timetable_gui.cpp

Go to the documentation of this file.

    00001 /* $Id: timetable_gui.cpp 15427 2009-02-09 02:33:10Z rubidium $ */
    00002
    00005 #include "stdafx.h"
    00006 #include "command_func.h"
    00007 #include "gui.h"
    00008 #include "window_gui.h"
    00009 #include "window_func.h"
    00010 #include "textbuf_gui.h"
    00011 #include "strings_func.h"
    00012 #include "vehicle_base.h"
    00013 #include "string_func.h"
    00014 #include "gfx_func.h"
    00015 #include "company_func.h"
    00016 #include "settings_type.h"
    00017
    00018 #include "table/strings.h"
    00019
    00020 enum TimetableViewWindowWidgets {
    00021   TTV_WIDGET_CLOSEBOX = 0,
    00022   TTV_CAPTION,
    00023   TTV_ORDER_VIEW,
    00024   TTV_STICKY,
    00025   TTV_TIMETABLE_PANEL,
    00026   TTV_SCROLLBAR,
    00027   TTV_SUMMARY_PANEL,
    00028   TTV_CHANGE_TIME,
    00029   TTV_CLEAR_TIME,
    00030   TTV_RESET_LATENESS,
    00031   TTV_AUTOFILL,
    00032   TTV_EMPTY,
    00033   TTV_RESIZE,
    00034 };
    00035
    00036 void SetTimetableParams(int param1, int param2, uint32 time)
    00037 {
    00038   if (_settings_client.gui.timetable_in_ticks) {
    00039     SetDParam(param1, STR_TIMETABLE_TICKS);
    00040     SetDParam(param2, time);
    00041   } else {
    00042     SetDParam(param1, STR_TIMETABLE_DAYS);
    00043     SetDParam(param2, time / DAY_TICKS);
    00044   }
    00045 }
    00046
    00047 struct TimetableWindow : Window {
    00048   int sel_index;
    00049   const Vehicle *vehicle;
    00050
    00051   TimetableWindow(const WindowDesc *desc, WindowNumber window_number) : Window(desc, window_number)
    00052   {
    00053     this->vehicle = GetVehicle(window_number);
    00054     this->owner = this->vehicle->owner;
    00055     this->vscroll.cap = 8;
    00056     this->resize.step_height = 10;
    00057     this->sel_index = -1;
    00058
    00059     this->FindWindowPlacementAndResize(desc);
    00060   }
    00061
    00062   int GetOrderFromTimetableWndPt(int y, const Vehicle *v)
    00063   {
    00064     /*
    00065      * Calculation description:
    00066      * 15 = 14 (this->widget[TTV_ORDER_VIEW].top) + 1 (frame-line)
    00067      * 10 = order text hight
    00068      */
    00069     int sel = (y - 15) / 10;
    00070
    00071     if ((uint)sel >= this->vscroll.cap) return INVALID_ORDER;
    00072
    00073     sel += this->vscroll.pos;
    00074
    00075     return (sel < v->GetNumOrders() * 2 && sel >= 0) ? sel : INVALID_ORDER;
    00076   }
    00077
    00078   virtual void OnInvalidateData(int data)
    00079   {
    00080     switch (data) {
    00081       case 0:
    00082         /* Autoreplace replaced the vehicle */
    00083         this->vehicle = GetVehicle(this->window_number);
    00084         break;
    00085
    00086       case -1:
    00087         /* Removed / replaced all orders (after deleting / sharing) */
    00088         if (this->sel_index == -1) break;
    00089
    00090         this->DeleteChildWindows();
    00091         this->sel_index = -1;
    00092         break;
    00093
    00094       default: {
    00095         /* Moving an order. If one of these is INVALID_VEH_ORDER_ID, then
    00096          * the order is being created / removed */
    00097         if (this->sel_index == -1) break;
    00098
    00099         VehicleOrderID from = GB(data, 0, 8);
    00100         VehicleOrderID to   = GB(data, 8, 8);
    00101
    00102         if (from == to) break; // no need to change anything
    00103
    00104         /* if from == INVALID_VEH_ORDER_ID, one order was added; if to == INVALID_VEH_ORDER_ID, one order was removed */
    00105         uint old_num_orders = this->vehicle->GetNumOrders() - (uint)(from == INVALID_VEH_ORDER_ID) + (uint)(to == INVALID_VEH_ORDER_ID);
    00106
    00107         VehicleOrderID selected_order = (this->sel_index + 1) / 2;
    00108         if (selected_order == old_num_orders) selected_order = 0; // when last travel time is selected, it belongs to order 0
    00109
    00110         bool travel = HasBit(this->sel_index, 0);
    00111
    00112         if (from != selected_order) {
    00113           /* Moving from preceeding order? */
    00114           selected_order -= (int)(from <= selected_order);
    00115           /* Moving to   preceeding order? */
    00116           selected_order += (int)(to   <= selected_order);
    00117         } else {
    00118           /* Now we are modifying the selected order */
    00119           if (to == INVALID_VEH_ORDER_ID) {
    00120             /* Deleting selected order */
    00121             this->DeleteChildWindows();
    00122             this->sel_index = -1;
    00123             break;
    00124           } else {
    00125             /* Moving selected order */
    00126             selected_order = to;
    00127           }
    00128         }
    00129
    00130         /* recompute new sel_index */
    00131         this->sel_index = 2 * selected_order - (int)travel;
    00132         /* travel time of first order needs special handling */
    00133         if (this->sel_index == -1) this->sel_index = this->vehicle->GetNumOrders() * 2 - 1;
    00134       } break;
    00135     }
    00136   }
    00137
    00138
    00139   virtual void OnPaint()
    00140   {
    00141     const Vehicle *v = this->vehicle;
    00142     int selected = this->sel_index;
    00143
    00144     SetVScrollCount(this, v->GetNumOrders() * 2);
    00145
    00146     if (v->owner == _local_company) {
    00147       bool disable = true;
    00148       if (selected != -1) {
    00149         const Order *order = GetVehicleOrder(v, ((selected + 1) / 2) % v->GetNumOrders());
    00150         if (selected % 2 == 1) {
    00151           disable = order != NULL && order->IsType(OT_CONDITIONAL);
    00152         } else {
    00153           disable = order == NULL || ((!order->IsType(OT_GOTO_STATION) || (order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) && !order->IsType(OT_CONDITIONAL));
    00154         }
    00155       }
    00156
    00157       this->SetWidgetDisabledState(TTV_CHANGE_TIME, disable);
    00158       this->SetWidgetDisabledState(TTV_CLEAR_TIME, disable);
    00159
    00160       this->EnableWidget(TTV_RESET_LATENESS);
    00161       this->EnableWidget(TTV_AUTOFILL);
    00162     } else {
    00163       this->DisableWidget(TTV_CHANGE_TIME);
    00164       this->DisableWidget(TTV_CLEAR_TIME);
    00165       this->DisableWidget(TTV_RESET_LATENESS);
    00166       this->DisableWidget(TTV_AUTOFILL);
    00167     }
    00168
    00169     this->SetWidgetLoweredState(TTV_AUTOFILL, HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE));
    00170
    00171     SetDParam(0, v->index);
    00172     this->DrawWidgets();
    00173
    00174     int y = 15;
    00175     int i = this->vscroll.pos;
    00176     VehicleOrderID order_id = (i + 1) / 2;
    00177     bool final_order = false;
    00178
    00179     const Order *order = GetVehicleOrder(v, order_id);
    00180
    00181     while (order != NULL) {
    00182       /* Don't draw anything if it extends past the end of the window. */
    00183       if (i - this->vscroll.pos >= this->vscroll.cap) break;
    00184
    00185       if (i % 2 == 0) {
    00186         DrawOrderString(v, order, order_id, y, i == selected, true, this->widget[TTV_TIMETABLE_PANEL].right - 4);
    00187
    00188         order_id++;
    00189
    00190         if (order_id >= v->GetNumOrders()) {
    00191           order = GetVehicleOrder(v, 0);
    00192           final_order = true;
    00193         } else {
    00194           order = order->next;
    00195         }
    00196       } else {
    00197         StringID string;
    00198
    00199         if (order->IsType(OT_CONDITIONAL)) {
    00200           string = STR_TIMETABLE_NO_TRAVEL;
    00201         } else if (order->travel_time == 0) {
    00202           string = STR_TIMETABLE_TRAVEL_NOT_TIMETABLED;
    00203         } else {
    00204           SetTimetableParams(0, 1, order->travel_time);
    00205           string = STR_TIMETABLE_TRAVEL_FOR;
    00206         }
    00207
    00208         DrawStringTruncated(2, y, string, (i == selected) ? TC_WHITE : TC_BLACK, this->widget[TTV_TIMETABLE_PANEL].right - 4);
    00209
    00210         if (final_order) break;
    00211       }
    00212
    00213       i++;
    00214       y += 10;
    00215     }
    00216
    00217     y = this->widget[TTV_SUMMARY_PANEL].top + 1;
    00218
    00219     {
    00220       uint total_time = 0;
    00221       bool complete = true;
    00222
    00223       for (const Order *order = GetVehicleOrder(v, 0); order != NULL; order = order->next) {
    00224         total_time += order->travel_time + order->wait_time;
    00225         if (order->travel_time == 0 && !order->IsType(OT_CONDITIONAL)) complete = false;
    00226         if (order->wait_time == 0 && order->IsType(OT_GOTO_STATION) && !(order->GetNonStopType() & ONSF_NO_STOP_AT_DESTINATION_STATION)) complete = false;
    00227       }
    00228
    00229       if (total_time != 0) {
    00230         SetTimetableParams(0, 1, total_time);
    00231         DrawString(2, y, complete ? STR_TIMETABLE_TOTAL_TIME : STR_TIMETABLE_TOTAL_TIME_INCOMPLETE, TC_BLACK);
    00232       }
    00233     }
    00234     y += 10;
    00235
    00236     if (v->lateness_counter == 0 || (!_settings_client.gui.timetable_in_ticks && v->lateness_counter / DAY_TICKS == 0)) {
    00237       DrawString(2, y, STR_TIMETABLE_STATUS_ON_TIME, TC_BLACK);
    00238     } else {
    00239       SetTimetableParams(0, 1, abs(v->lateness_counter));
    00240       DrawString(2, y, v->lateness_counter < 0 ? STR_TIMETABLE_STATUS_EARLY : STR_TIMETABLE_STATUS_LATE, TC_BLACK);
    00241     }
    00242   }
    00243
    00244   static inline uint32 PackTimetableArgs(const Vehicle *v, uint selected)
    00245   {
    00246     uint order_number = (selected + 1) / 2;
    00247     uint is_journey   = (selected % 2 == 1) ? 1 : 0;
    00248
    00249     if (order_number >= v->GetNumOrders()) order_number = 0;
    00250
    00251     return v->index | (order_number << 16) | (is_journey << 24);
    00252   }
    00253
    00254   virtual void OnClick(Point pt, int widget)
    00255   {
    00256     const Vehicle *v = this->vehicle;
    00257
    00258     switch (widget) {
    00259       case TTV_ORDER_VIEW: /* Order view button */
    00260         ShowOrdersWindow(v);
    00261         break;
    00262
    00263       case TTV_TIMETABLE_PANEL: { /* Main panel. */
    00264         int selected = GetOrderFromTimetableWndPt(pt.y, v);
    00265
    00266         this->DeleteChildWindows();
    00267         this->sel_index = (selected == INVALID_ORDER || selected == this->sel_index) ? -1 : selected;
    00268       } break;
    00269
    00270       case TTV_CHANGE_TIME: { /* "Wait For" button. */
    00271         int selected = this->sel_index;
    00272         VehicleOrderID real = (selected + 1) / 2;
    00273
    00274         if (real >= v->GetNumOrders()) real = 0;
    00275
    00276         const Order *order = GetVehicleOrder(v, real);
    00277         StringID current = STR_EMPTY;
    00278
    00279         if (order != NULL) {
    00280           uint time = (selected % 2 == 1) ? order->travel_time : order->wait_time;
    00281           if (!_settings_client.gui.timetable_in_ticks) time /= DAY_TICKS;
    00282
    00283           if (time != 0) {
    00284             SetDParam(0, time);
    00285             current = STR_CONFIG_SETTING_INT32;
    00286           }
    00287         }
    00288
    00289         ShowQueryString(current, STR_TIMETABLE_CHANGE_TIME, 31, 150, this, CS_NUMERAL, QSF_NONE);
    00290       } break;
    00291
    00292       case TTV_CLEAR_TIME: { /* Clear waiting time button. */
    00293         uint32 p1 = PackTimetableArgs(v, this->sel_index);
    00294         DoCommandP(0, p1, 0, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE));
    00295       } break;
    00296
    00297       case TTV_RESET_LATENESS: /* Reset the vehicle's late counter. */
    00298         DoCommandP(0, v->index, 0, CMD_SET_VEHICLE_ON_TIME | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE));
    00299         break;
    00300
    00301       case TTV_AUTOFILL: { /* Autofill the timetable. */
    00302         uint32 p2 = 0;
    00303         if (!HasBit(v->vehicle_flags, VF_AUTOFILL_TIMETABLE)) SetBit(p2, 0);
    00304         if (_ctrl_pressed) SetBit(p2, 1);
    00305         DoCommandP(0, v->index, p2, CMD_AUTOFILL_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE));
    00306       } break;
    00307     }
    00308
    00309     this->SetDirty();
    00310   }
    00311
    00312   virtual void OnQueryTextFinished(char *str)
    00313   {
    00314     if (str == NULL) return;
    00315
    00316     const Vehicle *v = this->vehicle;
    00317
    00318     uint32 p1 = PackTimetableArgs(v, this->sel_index);
    00319
    00320     uint64 time = StrEmpty(str) ? 0 : strtoul(str, NULL, 10);
    00321     if (!_settings_client.gui.timetable_in_ticks) time *= DAY_TICKS;
    00322
    00323     uint32 p2 = minu(time, UINT16_MAX);
    00324
    00325     DoCommandP(0, p1, p2, CMD_CHANGE_TIMETABLE | CMD_MSG(STR_CAN_T_TIMETABLE_VEHICLE));
    00326   }
    00327
    00328   virtual void OnResize(Point new_size, Point delta)
    00329   {
    00330     /* Update the scroll + matrix */
    00331     this->vscroll.cap = (this->widget[TTV_TIMETABLE_PANEL].bottom - this->widget[TTV_TIMETABLE_PANEL].top) / 10;
    00332   }
    00333 };
    00334
    00335 static const Widget _timetable_widgets[] = {
    00336   {   WWT_CLOSEBOX,   RESIZE_NONE,   COLOUR_GREY,     0,    10,     0,    13, STR_00C5,                   STR_018B_CLOSE_WINDOW},                // TTV_WIDGET_CLOSEBOX
    00337   {    WWT_CAPTION,   RESIZE_RIGHT,  COLOUR_GREY,    11,   326,     0,    13, STR_TIMETABLE_TITLE,        STR_018C_WINDOW_TITLE_DRAG_THIS},      // TTV_CAPTION
    00338   { WWT_PUSHTXTBTN,   RESIZE_LR,     COLOUR_GREY,   327,   387,     0,    13, STR_ORDER_VIEW,             STR_ORDER_VIEW_TOOLTIP},               // TTV_ORDER_VIEW
    00339   {  WWT_STICKYBOX,   RESIZE_LR,     COLOUR_GREY,   388,   399,     0,    13, STR_NULL,                   STR_STICKY_BUTTON},                    // TTV_STICKY
    00340
    00341   {      WWT_PANEL,   RESIZE_RB,     COLOUR_GREY,     0,   387,    14,    95, STR_NULL,                   STR_TIMETABLE_TOOLTIP},                // TTV_TIMETABLE_PANEL
    00342   {  WWT_SCROLLBAR,   RESIZE_LRB,    COLOUR_GREY,   388,   399,    14,    95, STR_NULL,                   STR_0190_SCROLL_BAR_SCROLLS_LIST},     // TTV_SCROLLBAR
    00343
    00344   {      WWT_PANEL,   RESIZE_RTB,    COLOUR_GREY,     0,   399,    96,   117, STR_NULL,                   STR_NULL},                             // TTV_SUMMARY_PANEL
    00345
    00346   { WWT_PUSHTXTBTN,   RESIZE_TB,     COLOUR_GREY,     0,   109,   118,   129, STR_TIMETABLE_CHANGE_TIME,  STR_TIMETABLE_WAIT_TIME_TOOLTIP},      // TTV_CHANGE_TIME
    00347   { WWT_PUSHTXTBTN,   RESIZE_TB,     COLOUR_GREY,   110,   219,   118,   129, STR_CLEAR_TIME,             STR_TIMETABLE_CLEAR_TIME_TOOLTIP},     // TTV_CLEAR_TIME
    00348   { WWT_PUSHTXTBTN,   RESIZE_TB,     COLOUR_GREY,   220,   337,   118,   129, STR_RESET_LATENESS,         STR_TIMETABLE_RESET_LATENESS_TOOLTIP}, // TTV_RESET_LATENESS
    00349   { WWT_PUSHTXTBTN,   RESIZE_TB,     COLOUR_GREY,   338,   387,   118,   129, STR_TIMETABLE_AUTOFILL,     STR_TIMETABLE_AUTOFILL_TOOLTIP},       // TTV_AUTOFILL
    00350
    00351   {      WWT_PANEL,   RESIZE_RTB,    COLOUR_GREY,   388,   387,   118,   129, STR_NULL,                   STR_NULL},                             // TTV_EMPTY
    00352   {  WWT_RESIZEBOX,   RESIZE_LRTB,   COLOUR_GREY,   388,   399,   118,   129, STR_NULL,                   STR_RESIZE_BUTTON},                    // TTV_RESIZE
    00353
    00354   {    WIDGETS_END }
    00355 };
    00356
    00357 static const WindowDesc _timetable_desc = {
    00358   WDP_AUTO, WDP_AUTO, 400, 130, 400, 130,
    00359   WC_VEHICLE_TIMETABLE, WC_VEHICLE_VIEW,
    00360   WDF_STD_TOOLTIPS | WDF_STD_BTN | WDF_DEF_WIDGET | WDF_UNCLICK_BUTTONS | WDF_STICKY_BUTTON | WDF_RESIZABLE | WDF_CONSTRUCTION,
    00361   _timetable_widgets,
    00362 };
    00363
    00364 void ShowTimetableWindow(const Vehicle *v)
    00365 {
    00366   DeleteWindowById(WC_VEHICLE_DETAILS, v->index, false);
    00367   DeleteWindowById(WC_VEHICLE_ORDERS, v->index, false);
    00368   AllocateWindowDescFront<TimetableWindow>(&_timetable_desc, v->index);
    00369 }