Main Page | Class Hierarchy | Alphabetical List | Class List | Directories | File List | Class Members | File Members

patchgui.C

Go to the documentation of this file.
00001 #include "automation.h"
00002 #include "bcsignals.h"
00003 #include "cplayback.h"
00004 #include "cwindow.h"
00005 #include "edl.h"
00006 #include "edlsession.h"
00007 #include "intauto.h"
00008 #include "intautos.h"
00009 #include "language.h"
00010 #include "localsession.h"
00011 #include "mainsession.h"
00012 #include "mainundo.h"
00013 #include "mwindow.h"
00014 #include "mwindowgui.h"
00015 #include "patchbay.h"
00016 #include "patchgui.h"
00017 #include "playbackengine.h"
00018 #include "theme.h"
00019 #include "track.h"
00020 #include "trackcanvas.h"
00021 #include "tracks.h"
00022 #include "transportque.h"
00023 #include "vframe.h"
00024 
00025 
00026 
00027 PatchGUI::PatchGUI(MWindow *mwindow, 
00028                 PatchBay *patchbay, 
00029                 Track *track, 
00030                 int x, 
00031                 int y)
00032 {
00033         this->mwindow = mwindow;
00034         this->patchbay = patchbay;
00035         this->track = track;
00036         this->x = x;
00037         this->y = y;
00038         title = 0;
00039         record = 0;
00040         play = 0;
00041 //      automate = 0;
00042         gang = 0;
00043         draw = 0;
00044         mute = 0;
00045         expand = 0;
00046         nudge = 0;
00047         change_source = 0;
00048         track_id = -1;
00049         if(track) track_id = track->get_id();
00050 }
00051 
00052 PatchGUI::~PatchGUI()
00053 {
00054         if(title) delete title;
00055         if(record) delete record;
00056         if(play) delete play;
00057 //      if(automate) delete automate;
00058         if(gang) delete gang;
00059         if(draw) delete draw;
00060         if(mute) delete mute;
00061         if(expand) delete expand;
00062         if(nudge) delete nudge;
00063 }
00064 
00065 int PatchGUI::create_objects()
00066 {
00067         return update(x, y);
00068 }
00069 
00070 int PatchGUI::reposition(int x, int y)
00071 {
00072         int x1 = 0;
00073         int y1 = 0;
00074 
00075 
00076         if(x != this->x || y != this->y)
00077         {
00078                 this->x = x;
00079                 this->y = y;
00080 
00081                 if(title)
00082                 {
00083 TRACE("PatchGUI::reposition 1\n");
00084                         title->reposition_window(x1, y1 + y);
00085 TRACE("PatchGUI::reposition 2\n");
00086                 }
00087                 y1 += mwindow->theme->title_h;
00088 
00089                 if(play)
00090                 {
00091 TRACE("PatchGUI::reposition 3\n");
00092                         play->reposition_window(x1, y1 + y);
00093                         x1 += play->get_w();
00094 TRACE("PatchGUI::reposition 4\n");
00095                         record->reposition_window(x1, y1 + y);
00096                         x1 += record->get_w();
00097 TRACE("PatchGUI::reposition 5\n");
00098 //                      automate->reposition_window(x1, y1 + y);
00099 //                      x1 += automate->get_w();
00100                         gang->reposition_window(x1, y1 + y);
00101                         x1 += gang->get_w();
00102 TRACE("PatchGUI::reposition 6\n");
00103                         draw->reposition_window(x1, y1 + y);
00104                         x1 += draw->get_w();
00105 TRACE("PatchGUI::reposition 7\n");
00106                         mute->reposition_window(x1, y1 + y);
00107                         x1 += mute->get_w();
00108 TRACE("PatchGUI::reposition 8\n");
00109 
00110                         if(expand)
00111                         {
00112 TRACE("PatchGUI::reposition 9\n");
00113                                 VFrame **expandpatch_data = mwindow->theme->get_image_set("expandpatch_data");
00114                                 expand->reposition_window(
00115                                         patchbay->get_w() - 10 - expandpatch_data[0]->get_w(), 
00116                                         y1 + y);
00117 TRACE("PatchGUI::reposition 10\n");
00118                                 x1 += expand->get_w();
00119 TRACE("PatchGUI::reposition 11\n");
00120                         }
00121                 }
00122                 y1 += mwindow->theme->play_h;
00123         }
00124         else
00125         {
00126                 y1 += mwindow->theme->title_h;
00127                 y1 += mwindow->theme->play_h;
00128         }
00129 
00130         return y1;
00131 }
00132 
00133 int PatchGUI::update(int x, int y)
00134 {
00135 //TRACE("PatchGUI::update 1");
00136         reposition(x, y);
00137 //TRACE("PatchGUI::update 10");
00138 
00139         int h = track->vertical_span(mwindow->theme);
00140         int y1 = 0;
00141         int x1 = 0;
00142 //printf("PatchGUI::update 10\n");
00143 
00144         if(title)
00145         {
00146                 if(h - y1 < 0)
00147                 {
00148                         delete title;
00149                         title = 0;
00150                 }
00151                 else
00152                 {
00153                         title->update(track->title);
00154                 }
00155         }
00156         else
00157         if(h - y1 >= 0)
00158         {
00159                 patchbay->add_subwindow(title = new TitlePatch(mwindow, this, x1 + x, y1 + y));
00160         }
00161         y1 += mwindow->theme->title_h;
00162 
00163         if(play)
00164         {
00165                 if(h - y1 < mwindow->theme->play_h)
00166                 {
00167                         delete play;
00168                         delete record;
00169                         delete gang;
00170                         delete draw;
00171                         delete mute;
00172                         delete expand;
00173                         play = 0;
00174                         record = 0;
00175                         draw = 0;
00176                         mute = 0;
00177                         expand = 0;
00178                 }
00179                 else
00180                 {
00181                         play->update(track->play);
00182                         record->update(track->record);
00183                         gang->update(track->gang);
00184                         draw->update(track->draw);
00185                         mute->update(mute->get_keyframe(mwindow, this)->value);
00186                         expand->update(track->expand_view);
00187                 }
00188         }
00189         else
00190         if(h - y1 >= mwindow->theme->play_h)
00191         {
00192                 patchbay->add_subwindow(play = new PlayPatch(mwindow, this, x1 + x, y1 + y));
00193 //printf("PatchGUI::update 1 %p %p\n", play, &play->status);
00194                 x1 += play->get_w();
00195                 patchbay->add_subwindow(record = new RecordPatch(mwindow, this, x1 + x, y1 + y));
00196                 x1 += record->get_w();
00197                 patchbay->add_subwindow(gang = new GangPatch(mwindow, this, x1 + x, y1 + y));
00198                 x1 += gang->get_w();
00199                 patchbay->add_subwindow(draw = new DrawPatch(mwindow, this, x1 + x, y1 + y));
00200                 x1 += draw->get_w();
00201                 patchbay->add_subwindow(mute = new MutePatch(mwindow, this, x1 + x, y1 + y));
00202                 x1 += mute->get_w();
00203 
00204                 VFrame **expandpatch_data = mwindow->theme->get_image_set("expandpatch_data");
00205                 patchbay->add_subwindow(expand = new ExpandPatch(mwindow, 
00206                         this, 
00207                         patchbay->get_w() - 10 - expandpatch_data[0]->get_w(), 
00208                         y1 + y));
00209                 x1 += expand->get_w();
00210         }
00211         y1 += mwindow->theme->play_h;
00212 
00213 //UNTRACE
00214         return y1;
00215 }
00216 
00217 
00218 void PatchGUI::toggle_behavior(int type, 
00219                 int value,
00220                 BC_Toggle *toggle,
00221                 int *output)
00222 {
00223         if(toggle->shift_down())
00224         {
00225                 int total_selected = mwindow->edl->tracks->total_of(type);
00226 
00227 // nothing previously selected
00228                 if(total_selected == 0)
00229                 {
00230                         mwindow->edl->tracks->select_all(type,
00231                                 1);
00232                 }
00233                 else
00234                 if(total_selected == 1)
00235                 {
00236 // this patch was previously the only one on
00237                         if(*output)
00238                         {
00239                                 mwindow->edl->tracks->select_all(type,
00240                                         1);
00241                         }
00242 // another patch was previously the only one on
00243                         else
00244                         {
00245                                 mwindow->edl->tracks->select_all(type,
00246                                         0);
00247                                 *output = 1;
00248                         }
00249                 }
00250                 else
00251                 if(total_selected > 1)
00252                 {
00253                         mwindow->edl->tracks->select_all(type,
00254                                 0);
00255                         *output = 1;
00256                 }
00257                 toggle->set_value(*output);
00258                 patchbay->update();
00259                 patchbay->drag_operation = type;
00260                 patchbay->new_status = 1;
00261                 patchbay->button_down = 1;
00262         }
00263         else
00264         {
00265                 *output = value;
00266 // Select + drag behavior
00267                 patchbay->drag_operation = type;
00268                 patchbay->new_status = value;
00269                 patchbay->button_down = 1;
00270         }
00271 
00272         switch(type)
00273         {
00274                 case Tracks::PLAY:
00275                         mwindow->gui->unlock_window();
00276                         mwindow->restart_brender();
00277                         mwindow->sync_parameters(CHANGE_EDL);
00278                         mwindow->gui->lock_window("PatchGUI::toggle_behavior 1");
00279                         mwindow->undo->update_undo(_("play patch"), LOAD_PATCHES);
00280                         break;
00281 
00282                 case Tracks::MUTE:
00283                         mwindow->gui->unlock_window();
00284                         mwindow->restart_brender();
00285                         mwindow->sync_parameters(CHANGE_PARAMS);
00286                         mwindow->gui->lock_window("PatchGUI::toggle_behavior 2");
00287                         mwindow->undo->update_undo(_("mute patch"), LOAD_PATCHES);
00288                         break;
00289 
00290 // Update affected tracks in cwindow
00291                 case Tracks::RECORD:
00292                         mwindow->cwindow->update(0, 1, 1);
00293                         mwindow->undo->update_undo(_("record patch"), LOAD_PATCHES);
00294                         break;
00295 
00296                 case Tracks::GANG:
00297                         mwindow->undo->update_undo(_("gang patch"), LOAD_PATCHES);
00298                         break;
00299 
00300                 case Tracks::DRAW:
00301                         mwindow->undo->update_undo(_("draw patch"), LOAD_PATCHES);
00302                         break;
00303 
00304                 case Tracks::EXPAND:
00305                         mwindow->undo->update_undo(_("expand patch"), LOAD_PATCHES);
00306                         break;
00307         }
00308 }
00309 
00310 
00311 char* PatchGUI::calculate_nudge_text(int *changed)
00312 {
00313         if(changed) *changed = 0;
00314         if(track->edl->session->nudge_seconds)
00315         {
00316                 sprintf(string_return, "%.4f", track->from_units(track->nudge));
00317                 if(changed && nudge && atof(nudge->get_text()) - atof(string_return) != 0)
00318                         *changed = 1;
00319         }
00320         else
00321         {
00322                 sprintf(string_return, "%d", track->nudge);
00323                 if(changed && nudge && atoi(nudge->get_text()) - atoi(string_return) != 0)
00324                         *changed = 1;
00325         }
00326         return string_return;
00327 }
00328 
00329 
00330 int64_t PatchGUI::calculate_nudge(char *string)
00331 {
00332         if(mwindow->edl->session->nudge_seconds)
00333         {
00334                 float result;
00335                 sscanf(string, "%f", &result);
00336                 return track->to_units(result, 0);
00337         }
00338         else
00339         {
00340                 int64_t temp;
00341                 sscanf(string, "%lld", &temp);
00342                 return temp;
00343         }
00344 }
00345 
00346 
00347 
00348 
00349 
00350 
00351 
00352 
00353 
00354 
00355 
00356 
00357 PlayPatch::PlayPatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00358  : BC_Toggle(x, 
00359                 y, 
00360                 mwindow->theme->get_image_set("playpatch_data"),
00361                 patch->track->play, 
00362                 "",
00363                 0,
00364                 0,
00365                 0)
00366 {
00367         this->mwindow = mwindow;
00368         this->patch = patch;
00369         set_tooltip(_("Play track"));
00370         set_select_drag(1);
00371 }
00372 
00373 int PlayPatch::button_press_event()
00374 {
00375         if(is_event_win() && get_buttonpress() == 1)
00376         {
00377                 set_status(BC_Toggle::TOGGLE_DOWN);
00378                 update(!get_value());
00379                 patch->toggle_behavior(Tracks::PLAY,
00380                         get_value(),
00381                         this,
00382                         &patch->track->play);
00383                 return 1;
00384         }
00385         return 0;
00386 }
00387 
00388 int PlayPatch::button_release_event()
00389 {
00390         int result = BC_Toggle::button_release_event();
00391         if(patch->patchbay->drag_operation != Tracks::NONE)
00392         {
00393                 patch->patchbay->drag_operation = Tracks::NONE;
00394         }
00395         return result;
00396 }
00397 
00398 
00399 
00400 
00401 
00402 
00403 
00404 
00405 
00406 
00407 
00408 RecordPatch::RecordPatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00409  : BC_Toggle(x, 
00410                 y, 
00411                 mwindow->theme->get_image_set("recordpatch_data"),
00412                 patch->track->record, 
00413                 "",
00414                 0,
00415                 0,
00416                 0)
00417 {
00418         this->mwindow = mwindow;
00419         this->patch = patch;
00420         set_tooltip(_("Arm track"));
00421         set_select_drag(1);
00422 }
00423 
00424 int RecordPatch::button_press_event()
00425 {
00426         if(is_event_win() && get_buttonpress() == 1)
00427         {
00428                 set_status(BC_Toggle::TOGGLE_DOWN);
00429                 update(!get_value());
00430                 patch->toggle_behavior(Tracks::RECORD,
00431                         get_value(),
00432                         this,
00433                         &patch->track->record);
00434                 return 1;
00435         }
00436         return 0;
00437 }
00438 
00439 int RecordPatch::button_release_event()
00440 {
00441         int result = BC_Toggle::button_release_event();
00442         if(patch->patchbay->drag_operation != Tracks::NONE)
00443         {
00444                 patch->patchbay->drag_operation = Tracks::NONE;
00445         }
00446         return result;
00447 }
00448 
00449 
00450 
00451 
00452 
00453 
00454 
00455 
00456 
00457 
00458 
00459 GangPatch::GangPatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00460  : BC_Toggle(x, y, 
00461                 mwindow->theme->get_image_set("gangpatch_data"),
00462                 patch->track->gang, 
00463                 "",
00464                 0,
00465                 0,
00466                 0)
00467 {
00468         this->mwindow = mwindow;
00469         this->patch = patch;
00470         set_tooltip(_("Gang faders"));
00471         set_select_drag(1);
00472 }
00473 
00474 int GangPatch::button_press_event()
00475 {
00476         if(is_event_win() && get_buttonpress() == 1)
00477         {
00478                 set_status(BC_Toggle::TOGGLE_DOWN);
00479                 update(!get_value());
00480                 patch->toggle_behavior(Tracks::GANG,
00481                         get_value(),
00482                         this,
00483                         &patch->track->gang);
00484                 return 1;
00485         }
00486         return 0;
00487 }
00488 
00489 int GangPatch::button_release_event()
00490 {
00491         int result = BC_Toggle::button_release_event();
00492         if(patch->patchbay->drag_operation != Tracks::NONE)
00493         {
00494                 patch->patchbay->drag_operation = Tracks::NONE;
00495         }
00496         return result;
00497 }
00498 
00499 
00500 
00501 
00502 
00503 
00504 
00505 
00506 
00507 
00508 
00509 DrawPatch::DrawPatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00510  : BC_Toggle(x, y, 
00511                 mwindow->theme->get_image_set("drawpatch_data"),
00512                 patch->track->draw, 
00513                 "",
00514                 0,
00515                 0,
00516                 0)
00517 {
00518         this->mwindow = mwindow;
00519         this->patch = patch;
00520         set_tooltip(_("Draw media"));
00521         set_select_drag(1);
00522 }
00523 
00524 int DrawPatch::button_press_event()
00525 {
00526         if(is_event_win() && get_buttonpress() == 1)
00527         {
00528                 set_status(BC_Toggle::TOGGLE_DOWN);
00529                 update(!get_value());
00530                 patch->toggle_behavior(Tracks::DRAW,
00531                         get_value(),
00532                         this,
00533                         &patch->track->draw);
00534                 return 1;
00535         }
00536         return 0;
00537 }
00538 
00539 int DrawPatch::button_release_event()
00540 {
00541         int result = BC_Toggle::button_release_event();
00542         if(patch->patchbay->drag_operation != Tracks::NONE)
00543         {
00544                 patch->patchbay->drag_operation = Tracks::NONE;
00545         }
00546         return result;
00547 }
00548 
00549 
00550 
00551 
00552 
00553 
00554 
00555 
00556 
00557 
00558 MutePatch::MutePatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00559  : BC_Toggle(x, y, 
00560                 mwindow->theme->get_image_set("mutepatch_data"),
00561                 get_keyframe(mwindow, patch)->value, 
00562                 "",
00563                 0,
00564                 0,
00565                 0)
00566 {
00567         this->mwindow = mwindow;
00568         this->patch = patch;
00569         set_tooltip(_("Don't send to output"));
00570         set_select_drag(1);
00571 }
00572 
00573 int MutePatch::button_press_event()
00574 {
00575         if(is_event_win() && get_buttonpress() == 1)
00576         {
00577                 set_status(BC_Toggle::TOGGLE_DOWN);
00578                 update(!get_value());
00579                 IntAuto *current;
00580                 double position = mwindow->edl->local_session->get_selectionstart(1);
00581                 Autos *mute_autos = patch->track->automation->autos[AUTOMATION_MUTE];
00582 
00583 
00584                 current = (IntAuto*)mute_autos->get_auto_for_editing(position);
00585                 current->value = get_value();
00586 
00587                 patch->toggle_behavior(Tracks::MUTE,
00588                         get_value(),
00589                         this,
00590                         &current->value);
00591 
00592 
00593                 mwindow->undo->update_undo(_("keyframe"), LOAD_AUTOMATION);
00594 
00595                 if(mwindow->edl->session->auto_conf->autos[AUTOMATION_MUTE])
00596                 {
00597                         mwindow->gui->canvas->draw_overlays();
00598                         mwindow->gui->canvas->flash();
00599                 }
00600                 return 1;
00601         }
00602         return 0;
00603 }
00604 
00605 int MutePatch::button_release_event()
00606 {
00607         int result = BC_Toggle::button_release_event();
00608         if(patch->patchbay->drag_operation != Tracks::NONE)
00609         {
00610                 patch->patchbay->drag_operation = Tracks::NONE;
00611         }
00612         return result;
00613 }
00614 
00615 IntAuto* MutePatch::get_keyframe(MWindow *mwindow, PatchGUI *patch)
00616 {
00617         Auto *current = 0;
00618         double unit_position = mwindow->edl->local_session->get_selectionstart(1);
00619         unit_position = mwindow->edl->align_to_frame(unit_position, 0);
00620         unit_position = patch->track->to_units(unit_position, 0);
00621         return (IntAuto*)patch->track->automation->autos[AUTOMATION_MUTE]->get_prev_auto(
00622                 (int64_t)unit_position, 
00623                 PLAY_FORWARD,
00624                 current);
00625 }
00626 
00627 
00628 
00629 
00630 
00631 
00632 
00633 
00634 
00635 
00636 
00637 
00638 ExpandPatch::ExpandPatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00639  : BC_Toggle(x, 
00640                 y, 
00641                 mwindow->theme->get_image_set("expandpatch_data"),
00642                 patch->track->expand_view, 
00643                 "",
00644                 0,
00645                 0,
00646                 0)
00647 {
00648         this->mwindow = mwindow;
00649         this->patch = patch;
00650         set_select_drag(1);
00651 }
00652 
00653 int ExpandPatch::button_press_event()
00654 {
00655         if(is_event_win() && get_buttonpress() == 1)
00656         {
00657                 set_status(BC_Toggle::TOGGLE_DOWN);
00658                 update(!get_value());
00659                 patch->toggle_behavior(Tracks::EXPAND,
00660                         get_value(),
00661                         this,
00662                         &patch->track->expand_view);
00663                 mwindow->trackmovement(mwindow->edl->local_session->track_start);
00664                 return 1;
00665         }
00666         return 0;
00667 }
00668 
00669 int ExpandPatch::button_release_event()
00670 {
00671         int result = BC_Toggle::button_release_event();
00672         if(patch->patchbay->drag_operation != Tracks::NONE)
00673         {
00674                 patch->patchbay->drag_operation = Tracks::NONE;
00675         }
00676         return result;
00677 }
00678 
00679 
00680 
00681 
00682 
00683 TitlePatch::TitlePatch(MWindow *mwindow, PatchGUI *patch, int x, int y)
00684  : BC_TextBox(x, 
00685                 y, 
00686                 patch->patchbay->get_w() - 10, 
00687                 1,
00688                 patch->track->title)
00689 {
00690         this->mwindow = mwindow;
00691         this->patch = patch;
00692 }
00693 
00694 int TitlePatch::handle_event()
00695 {
00696         strcpy(patch->track->title, get_text());
00697         mwindow->update_plugin_titles();
00698         mwindow->gui->canvas->draw_overlays();
00699         mwindow->gui->canvas->flash();
00700         mwindow->undo->update_undo(_("track title"), LOAD_PATCHES);
00701         return 1;
00702 }
00703 
00704 
00705 
00706 
00707 
00708 
00709 
00710 
00711 
00712 NudgePatch::NudgePatch(MWindow *mwindow, 
00713         PatchGUI *patch, 
00714         int x, 
00715         int y, 
00716         int w)
00717  : BC_TextBox(x,
00718         y,
00719         w,
00720         1,
00721         patch->calculate_nudge_text(0))
00722 {
00723         this->mwindow = mwindow;
00724         this->patch = patch;
00725         set_tooltip(_("Nudge"));
00726 }
00727 
00728 int NudgePatch::handle_event()
00729 {
00730         set_value(patch->calculate_nudge(get_text()));
00731         return 1;
00732 }
00733 
00734 void NudgePatch::set_value(int64_t value)
00735 {
00736         patch->track->nudge = value;
00737 
00738         if(patch->track->gang)
00739                 patch->patchbay->synchronize_nudge(patch->track->nudge, patch->track);
00740 
00741         mwindow->undo->update_undo("nudge", LOAD_AUTOMATION, this);
00742 
00743         mwindow->gui->unlock_window();
00744         if(patch->track->data_type == TRACK_VIDEO)
00745                 mwindow->restart_brender();
00746         mwindow->sync_parameters(CHANGE_PARAMS);
00747         mwindow->gui->lock_window("NudgePatch::handle_event 2");
00748 
00749         mwindow->session->changes_made = 1;
00750 }
00751 
00752 
00753 int NudgePatch::button_press_event()
00754 {
00755         int result = 0;
00756 
00757         if(is_event_win() && cursor_inside())
00758         {
00759                 if(get_buttonpress() == 4)
00760                 {
00761                         int value = patch->calculate_nudge(get_text());
00762                         value += calculate_increment();
00763                         set_value(value);
00764                         update();
00765                         result = 1;
00766                 }
00767                 else
00768                 if(get_buttonpress() == 5)
00769                 {
00770                         int value = patch->calculate_nudge(get_text());
00771                         value -= calculate_increment();
00772                         set_value(value);
00773                         update();
00774                         result = 1;
00775                 }
00776                 else
00777                 if(get_buttonpress() == 3)
00778                 {
00779                         patch->patchbay->nudge_popup->activate_menu(patch);
00780                         result = 1;
00781                 }
00782         }
00783 
00784         if(!result)
00785                 return BC_TextBox::button_press_event();
00786         else
00787                 return result;
00788 }
00789 
00790 int64_t NudgePatch::calculate_increment()
00791 {
00792         if(patch->track->data_type == TRACK_AUDIO)
00793         {
00794                 return (int64_t)ceil(patch->track->edl->session->sample_rate / 10);
00795         }
00796         else
00797         {
00798                 return (int64_t)ceil(1.0 / patch->track->edl->session->frame_rate);
00799         }
00800 }
00801 
00802 void NudgePatch::update()
00803 {
00804         int changed;
00805         char *string = patch->calculate_nudge_text(&changed);
00806         if(changed)
00807                 BC_TextBox::update(string);
00808 }
00809 
00810 
00811 
00812 

Generated on Sun Jan 8 13:38:58 2006 for Cinelerra-svn by  doxygen 1.4.4