Signed-off-by: falkTX <falktx@falktx.com>tags/22.02
| @@ -497,15 +497,7 @@ struct HostMIDICC : Module { | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| struct CardinalMIDILearnPJ301MPort : PJ301MPort { | |||
| void onDragStart(const DragStartEvent& e) override { | |||
| PJ301MPort::onDragStart(e); | |||
| } | |||
| void onDragEnd(const DragEndEvent& e) override { | |||
| PJ301MPort::onDragEnd(e); | |||
| } | |||
| }; | |||
| #ifndef HEADLESS | |||
| /** | |||
| * Based on VCVRack's CcChoice as defined in src/core/plugin.hpp | |||
| * Copyright (C) 2016-2021 VCV. | |||
| @@ -523,7 +515,14 @@ struct CardinalCcChoice : CardinalLedDisplayChoice { | |||
| CardinalCcChoice(HostMIDICC* const m, const int i) | |||
| : CardinalLedDisplayChoice(), | |||
| module(m), | |||
| id(i) {} | |||
| id(i) | |||
| { | |||
| // Module browser setup | |||
| if (m == nullptr) | |||
| { | |||
| text = string::f("%d", i+1); | |||
| } | |||
| } | |||
| void step() override | |||
| { | |||
| @@ -691,14 +690,14 @@ struct HostMIDICCWidget : ModuleWidget { | |||
| { | |||
| const float x = startX_In + int(i / 6) * padding; | |||
| const float y = startY + int(i % 6) * padding; | |||
| addInput(createInput<CardinalMIDILearnPJ301MPort>(Vec(x, y), module, i)); | |||
| addInput(createInput<PJ301MPort>(Vec(x, y), module, i)); | |||
| } | |||
| for (int i=0; i<18; ++i) | |||
| { | |||
| const float x = startX_Out + int(i / 6) * padding; | |||
| const float y = startY + int(i % 6) * padding; | |||
| addOutput(createOutput<CardinalMIDILearnPJ301MPort>(Vec(x, y), module, i)); | |||
| addOutput(createOutput<PJ301MPort>(Vec(x, y), module, i)); | |||
| } | |||
| CCGridDisplay* const display = createWidget<CCGridDisplay>(Vec(startX_In - 3.0f, 70.0f)); | |||
| @@ -775,6 +774,9 @@ struct HostMIDICCWidget : ModuleWidget { | |||
| menu->addChild(outputChannelItem); | |||
| } | |||
| }; | |||
| #else | |||
| typedef ModuleWidget HostMIDICCWidget; | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -404,15 +404,7 @@ struct HostMIDIGate : Module { | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| struct CardinalMIDILearnPJ301MPort : PJ301MPort { | |||
| void onDragStart(const DragStartEvent& e) override { | |||
| PJ301MPort::onDragStart(e); | |||
| } | |||
| void onDragEnd(const DragEndEvent& e) override { | |||
| PJ301MPort::onDragEnd(e); | |||
| } | |||
| }; | |||
| #ifndef HEADLESS | |||
| /** | |||
| * Based on VCVRack's NoteChoice as defined in src/core/plugin.hpp | |||
| * Copyright (C) 2016-2021 VCV. | |||
| @@ -605,14 +597,14 @@ struct HostMIDIGateWidget : ModuleWidget { | |||
| { | |||
| const float x = startX_In + int(i / 6) * padding; | |||
| const float y = startY + int(i % 6) * padding; | |||
| addInput(createInput<CardinalMIDILearnPJ301MPort>(Vec(x, y), module, i)); | |||
| addInput(createInput<PJ301MPort>(Vec(x, y), module, i)); | |||
| } | |||
| for (int i=0; i<18; ++i) | |||
| { | |||
| const float x = startX_Out + int(i / 6) * padding; | |||
| const float y = startY + int(i % 6) * padding; | |||
| addOutput(createOutput<CardinalMIDILearnPJ301MPort>(Vec(x, y), module, i)); | |||
| addOutput(createOutput<PJ301MPort>(Vec(x, y), module, i)); | |||
| } | |||
| NoteGridDisplay* const display = createWidget<NoteGridDisplay>(Vec(startX_In - 3.0f, 70.0f)); | |||
| @@ -696,6 +688,9 @@ struct HostMIDIGateWidget : ModuleWidget { | |||
| )); | |||
| } | |||
| }; | |||
| #else | |||
| typedef ModuleWidget HostMIDIGateWidget; | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -90,10 +90,10 @@ struct HostMIDIMap : Module { | |||
| config(NUM_PARAMS, NUM_INPUTS, NUM_OUTPUTS, NUM_LIGHTS); | |||
| for (int id = 0; id < MAX_MIDI_CONTROL; ++id) | |||
| // { | |||
| // paramHandles[id].color = nvgRGB(0xff, 0xff, 0x40); | |||
| { | |||
| paramHandles[id].color = nvgRGBf(0.76f, 0.11f, 0.22f); | |||
| pcontext->engine->addParamHandle(¶mHandles[id]); | |||
| // } | |||
| } | |||
| for (int i = 0; i < MAX_MIDI_CONTROL; i++) | |||
| valueFilters[i].setTau(1 / 30.f); | |||
| @@ -460,18 +460,27 @@ struct HostMIDIMap : Module { | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| struct MIDIMapChoice : CardinalLedDisplayChoice { | |||
| #ifndef HEADLESS | |||
| struct CardinalMIDIMapChoice : CardinalLedDisplayChoice { | |||
| HostMIDIMap* const module; | |||
| const int id; | |||
| int disableLearnFrames = -1; | |||
| ParamWidget* lastTouchedParam = nullptr; | |||
| MIDIMapChoice(HostMIDIMap* const m, const int i) | |||
| CardinalMIDIMapChoice(HostMIDIMap* const m, const int i) | |||
| : CardinalLedDisplayChoice(), | |||
| module(m), | |||
| id(i) | |||
| { | |||
| alignTextCenter = false; | |||
| // Module browser setup | |||
| if (m == nullptr) | |||
| { | |||
| bgColor = nvgRGB(0, 0, 0); | |||
| color.a = 0.75f; | |||
| text = "Click here to map"; | |||
| } | |||
| } | |||
| void draw(const DrawArgs& args) override | |||
| @@ -489,7 +498,7 @@ struct MIDIMapChoice : CardinalLedDisplayChoice { | |||
| void step() override | |||
| { | |||
| if (!module) | |||
| if (module == nullptr) | |||
| return; | |||
| // Set bgColor and selected state | |||
| @@ -609,7 +618,7 @@ struct MIDIMapChoice : CardinalLedDisplayChoice { | |||
| struct HostMIDIMapDisplay : Widget { | |||
| HostMIDIMap* module; | |||
| ScrollWidget* scroll; | |||
| MIDIMapChoice* choices[MAX_MIDI_CONTROL]; | |||
| CardinalMIDIMapChoice* choices[MAX_MIDI_CONTROL]; | |||
| LedDisplaySeparator* separators[MAX_MIDI_CONTROL]; | |||
| void drawLayer(const DrawArgs& args, int layer) override | |||
| @@ -634,13 +643,15 @@ struct HostMIDIMapDisplay : Widget { | |||
| { | |||
| LedDisplaySeparator* separator = createWidget<LedDisplaySeparator>(Vec(0.0f, posY)); | |||
| separator->box.size = Vec(box.size.x, 1.0f); | |||
| separator->visible = false; | |||
| scroll->container->addChild(separator); | |||
| separators[id] = separator; | |||
| } | |||
| MIDIMapChoice* const choice = new MIDIMapChoice(module, id); | |||
| CardinalMIDIMapChoice* const choice = new CardinalMIDIMapChoice(module, id); | |||
| choice->box.pos = Vec(0.0f, posY); | |||
| choice->box.size = Vec(box.size.x, 20.0f); | |||
| choice->visible = id == 0; | |||
| scroll->container->addChild(choice); | |||
| choices[id] = choice; | |||
| @@ -722,6 +733,9 @@ struct HostMIDIMapWidget : ModuleWidget { | |||
| menu->addChild(inputChannelItem); | |||
| } | |||
| }; | |||
| #else | |||
| typedef ModuleWidget HostMIDIMapWidget; | |||
| #endif | |||
| // -------------------------------------------------------------------------------------------------------------------- | |||
| @@ -745,25 +745,33 @@ struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread { | |||
| : ImGuiWidget(), | |||
| module(m) | |||
| { | |||
| if (module->fCarlaHostHandle == nullptr) | |||
| { | |||
| fDrawingState = kDrawingErrorInit; | |||
| fIdleState = kIdleNothing; | |||
| fPopupError = "Ildaeil backend failed to init properly, cannot continue."; | |||
| return; | |||
| } | |||
| std::strcpy(fPluginSearchString, "Search..."); | |||
| if (checkIfPluginIsLoaded()) | |||
| fIdleState = kIdleInitPluginAlreadyLoaded; | |||
| if (m != nullptr) | |||
| { | |||
| if (m->fCarlaHostHandle == nullptr) | |||
| { | |||
| fDrawingState = kDrawingErrorInit; | |||
| fIdleState = kIdleNothing; | |||
| fPopupError = "Ildaeil backend failed to init properly, cannot continue."; | |||
| return; | |||
| } | |||
| if (checkIfPluginIsLoaded()) | |||
| fIdleState = kIdleInitPluginAlreadyLoaded; | |||
| module->fUI = this; | |||
| m->fUI = this; | |||
| } | |||
| else | |||
| { | |||
| fDrawingState = kDrawingPluginList; | |||
| fIdleState = kIdleNothing; | |||
| } | |||
| } | |||
| ~IldaeilWidget() override | |||
| { | |||
| if (module->fCarlaHostHandle != nullptr) | |||
| if (module != nullptr && module->fCarlaHostHandle != nullptr) | |||
| { | |||
| if (idleCallbackActive) | |||
| { | |||
| @@ -1009,6 +1017,9 @@ struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread { | |||
| void widgetCreated() | |||
| { | |||
| if (module == nullptr) | |||
| return; | |||
| if (const CarlaHostHandle handle = module->fCarlaHostHandle) | |||
| { | |||
| const CardinalPluginContext* const pcontext = module->pcontext; | |||
| @@ -1031,6 +1042,9 @@ struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread { | |||
| void widgetDestroyed() | |||
| { | |||
| if (module == nullptr) | |||
| return; | |||
| if (const CarlaHostHandle handle = module->fCarlaHostHandle) | |||
| { | |||
| const CardinalPluginContext* const pcontext = module->pcontext; | |||
| @@ -1532,7 +1546,7 @@ struct IldaeilWidget : ImGuiWidget, IdleCallback, Thread { | |||
| if (ImGui::Button("Load Plugin")) | |||
| fIdleState = kIdleLoadSelectedPlugin; | |||
| if (fPluginType != PLUGIN_INTERNAL && module->canUseBridges) | |||
| if (fPluginType != PLUGIN_INTERNAL && (module == nullptr || module->canUseBridges)) | |||
| { | |||
| ImGui::SameLine(); | |||
| ImGui::Checkbox("Run in bridge mode", &fPluginWillRunInBridgeMode); | |||
| @@ -1657,7 +1671,7 @@ struct IldaeilModuleWidget : ModuleWidget { | |||
| setModule(module); | |||
| setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/Ildaeil.svg"))); | |||
| if (module != nullptr && module->pcontext != nullptr) | |||
| if (module == nullptr || module->pcontext != nullptr) | |||
| { | |||
| ildaeilWidget = new IldaeilWidget(module); | |||
| ildaeilWidget->box.pos = Vec(2 * RACK_GRID_WIDTH, 0); | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * Dear ImGui for DPF, converted to VCV | |||
| * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| @@ -145,7 +145,7 @@ float ImGuiWidget::getScaleFactor() const noexcept | |||
| void ImGuiWidget::onContextCreate(const ContextCreateEvent& e) | |||
| { | |||
| OpenGlWidget::onContextCreate(e); | |||
| OpenGlWidgetWithBrowserPreview::onContextCreate(e); | |||
| DISTRHO_SAFE_ASSERT_RETURN(!imData->created,); | |||
| ImGui::SetCurrentContext(imData->context); | |||
| @@ -162,7 +162,7 @@ void ImGuiWidget::onContextDestroy(const ContextDestroyEvent& e) | |||
| imData->created = false; | |||
| } | |||
| OpenGlWidget::onContextDestroy(e); | |||
| OpenGlWidgetWithBrowserPreview::onContextDestroy(e); | |||
| } | |||
| void ImGuiWidget::setAsCurrentContext() | |||
| @@ -308,11 +308,37 @@ void ImGuiWidget::onSelectText(const SelectTextEvent& e) | |||
| void ImGuiWidget::drawFramebuffer() | |||
| { | |||
| const float scaleFactor = APP->window->pixelRatio; | |||
| drawFramebufferCommon(getFramebufferSize(), scaleFactor); | |||
| } | |||
| void ImGuiWidget::drawFramebufferForBrowserPreview() | |||
| { | |||
| if (imData->created) | |||
| { | |||
| ImGui::SetCurrentContext(imData->context); | |||
| ImGui_ImplOpenGL2_Shutdown(); | |||
| ImGui::DestroyContext(imData->context); | |||
| imData->created = false; | |||
| imData->fontGenerated = false; | |||
| imData->originalScaleFactor = 0.0f; | |||
| imData->scaleFactor = 0.0f; | |||
| } | |||
| imData->context = ImGui::CreateContext(); | |||
| ImGui::SetCurrentContext(imData->context); | |||
| ImGuiIO& io(ImGui::GetIO()); | |||
| ImGui_ImplOpenGL2_Init(); | |||
| imData->created = true; | |||
| const math::Vec fbSize = getFramebufferSize(); | |||
| const float scaleFactor = APP->window->pixelRatio; | |||
| drawFramebufferCommon(box.size.mult(oversample), oversample); | |||
| } | |||
| void ImGuiWidget::drawFramebufferCommon(const Vec& fbSize, const float scaleFactor) | |||
| { | |||
| ImGui::SetCurrentContext(imData->context); | |||
| ImGuiIO& io(ImGui::GetIO()); | |||
| if (d_isNotEqual(imData->scaleFactor, scaleFactor)) | |||
| { | |||
| @@ -1,6 +1,6 @@ | |||
| /* | |||
| * Dear ImGui for DPF, converted to VCV | |||
| * Copyright (C) 2021 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2021-2022 Filipe Coelho <falktx@falktx.com> | |||
| * Copyright (C) 2021 Jean Pierre Cimalando <jp-dev@inbox.ru> | |||
| * | |||
| * Permission to use, copy, modify, and/or distribute this software for any purpose with | |||
| @@ -20,7 +20,7 @@ | |||
| #include "plugin.hpp" | |||
| #include "DearImGui/imgui.h" | |||
| struct ImGuiWidget : OpenGlWidget { | |||
| struct ImGuiWidget : OpenGlWidgetWithBrowserPreview { | |||
| struct PrivateData; | |||
| PrivateData* const imData; | |||
| @@ -50,4 +50,6 @@ protected: | |||
| private: | |||
| void drawFramebuffer() override; | |||
| void drawFramebufferForBrowserPreview() override; | |||
| void drawFramebufferCommon(const Vec& fbSize, float scaleFactor); | |||
| }; | |||
| @@ -326,21 +326,24 @@ struct TextEditorModuleWidget : ModuleWidget { | |||
| addChild(rightHandle = new ModuleResizeHandle(module, this, true)); | |||
| addChild(new ModuleResizeHandle(module, this, false)); | |||
| box.size = Vec(RACK_GRID_WIDTH * (module != nullptr ? module->width : DEFAULT_WIDTH), RACK_GRID_HEIGHT); | |||
| textEditorModule = module; | |||
| textEditorWidget = new ImGuiTextEditor(); | |||
| textEditorWidget->box.pos = Vec(RACK_GRID_WIDTH, 0); | |||
| textEditorWidget->box.size = Vec(box.size.x - 2 * RACK_GRID_WIDTH, box.size.y); | |||
| addChild(textEditorWidget); | |||
| if (module != nullptr) | |||
| { | |||
| box.size = Vec(RACK_GRID_WIDTH * module->width, RACK_GRID_HEIGHT); | |||
| textEditorModule = module; | |||
| textEditorWidget = new ImGuiTextEditor(); | |||
| textEditorWidget->setFileWithKnownText(module->file, module->text); | |||
| textEditorWidget->setLanguageDefinition(module->lang); | |||
| textEditorWidget->box.pos = Vec(RACK_GRID_WIDTH, 0); | |||
| textEditorWidget->box.size = Vec((module->width - 2) * RACK_GRID_WIDTH, box.size.y); | |||
| addChild(textEditorWidget); | |||
| textEditorWidget->setFileWithKnownText(module->file, module->text); | |||
| module->widgetPtr = textEditorWidget; | |||
| } | |||
| else | |||
| { | |||
| box.size = Vec(RACK_GRID_WIDTH * DEFAULT_WIDTH, RACK_GRID_HEIGHT); | |||
| textEditorWidget->setLanguageDefinition(DEFAULT_LANG); | |||
| textEditorWidget->setText(DEFAULT_TEXT); | |||
| } | |||
| } | |||
| @@ -61,13 +61,13 @@ struct glBarsModule : Module { | |||
| }; | |||
| #ifndef HEADLESS | |||
| struct glBarsRendererWidget : OpenGlWidget { | |||
| struct glBarsRendererWidget : OpenGlWidgetWithBrowserPreview { | |||
| glBarsModule* const glBars; | |||
| glBarsRendererWidget(glBarsModule* const module) | |||
| : glBars(module) | |||
| { | |||
| if (APP->window->pixelRatio < 2.0f) | |||
| if (glBars != nullptr && APP->window->pixelRatio < 2.0f) | |||
| oversample = 2.0f; | |||
| } | |||
| @@ -75,22 +75,34 @@ struct glBarsRendererWidget : OpenGlWidget { | |||
| { | |||
| } | |||
| void drawLayer(const DrawArgs& args, int layer) override | |||
| void drawLayer(const DrawArgs& args, const int layer) override | |||
| { | |||
| if (layer != 1) | |||
| return; | |||
| OpenGlWidget::draw(args); | |||
| OpenGlWidgetWithBrowserPreview::draw(args); | |||
| } | |||
| void drawFramebuffer() override { | |||
| math::Vec fbSize = getFramebufferSize(); | |||
| void drawFramebuffer() override | |||
| { | |||
| DISTRHO_SAFE_ASSERT_RETURN(glBars != nullptr,); | |||
| drawFramebuffer(glBars->state, getFramebufferSize()); | |||
| } | |||
| void drawFramebufferForBrowserPreview() override | |||
| { | |||
| glBarsState state; | |||
| drawFramebuffer(state, box.size); | |||
| } | |||
| void drawFramebuffer(glBarsState& state, const Vec& fbSize) | |||
| { | |||
| glDisable(GL_BLEND); | |||
| glMatrixMode(GL_PROJECTION); | |||
| glPushMatrix(); | |||
| glLoadIdentity(); | |||
| glViewport(0.0, -100, fbSize.x * oversample, fbSize.y * oversample); | |||
| glViewport(0.0, -50 * oversample, fbSize.x * oversample, fbSize.y * oversample); | |||
| glFrustum(-1, 1, -1, 1, 1.5, 10); | |||
| glMatrixMode(GL_MODELVIEW); | |||
| glPushMatrix(); | |||
| @@ -99,7 +111,7 @@ struct glBarsRendererWidget : OpenGlWidget { | |||
| glClearColor(0.0f, 0.0f, 0.0f, 0.0f); | |||
| glClear(GL_COLOR_BUFFER_BIT); | |||
| glBars->state.Render(); | |||
| state.Render(); | |||
| glPopMatrix(); | |||
| glMatrixMode(GL_PROJECTION); | |||
| @@ -107,18 +119,17 @@ struct glBarsRendererWidget : OpenGlWidget { | |||
| glEnable(GL_BLEND); | |||
| } | |||
| void step() override { | |||
| void step() override | |||
| { | |||
| OpenGlWidget::step(); | |||
| oversample = APP->window->pixelRatio < 2.0f ? 2.0f : 1.0f; | |||
| if (glBars != nullptr) | |||
| oversample = APP->window->pixelRatio < 2.0f ? 2.0f : 1.0f; | |||
| } | |||
| }; | |||
| struct glBarsWidget : ModuleWidget { | |||
| glBarsRendererWidget* const glBarsRenderer; | |||
| glBarsWidget(glBarsModule* const module) | |||
| : glBarsRenderer(new glBarsRendererWidget(module)) | |||
| { | |||
| setModule(module); | |||
| setPanel(APP->window->loadSvg(asset::plugin(pluginInstance, "res/glBars.svg"))); | |||
| @@ -131,6 +142,7 @@ struct glBarsWidget : ModuleWidget { | |||
| addInput(createInput<PJ301MPort>(Vec(135.0f, 20.0f), module, glBarsModule::IN1_INPUT)); | |||
| const float size = mm2px(127.0f); | |||
| glBarsRendererWidget* const glBarsRenderer = new glBarsRendererWidget(module); | |||
| glBarsRenderer->box.pos = Vec((box.size.x - size) * 0.5f, (box.size.y - size) * 0.5f); | |||
| glBarsRenderer->box.size = Vec(size, size); | |||
| addChild(glBarsRenderer); | |||
| @@ -25,6 +25,7 @@ | |||
| using namespace rack; | |||
| #ifndef HEADLESS | |||
| struct CardinalLedDisplayChoice : LedDisplayChoice { | |||
| bool alignTextCenter = true; | |||
| @@ -60,6 +61,59 @@ struct CardinalLedDisplayChoice : LedDisplayChoice { | |||
| } | |||
| }; | |||
| struct OpenGlWidgetWithBrowserPreview : OpenGlWidget { | |||
| NVGLUframebuffer* fb = nullptr; | |||
| void draw(const DrawArgs& args) override | |||
| { | |||
| if (args.fb == nullptr) | |||
| return OpenGlWidget::draw(args); | |||
| // set oversample to current scale | |||
| float trans[6]; | |||
| nvgCurrentTransform(args.vg, trans); | |||
| oversample = std::max(1.0f, trans[0]); | |||
| // recreate framebuffer | |||
| deleteFramebuffer(); | |||
| fb = nvgluCreateFramebuffer(args.vg, box.size.x * oversample, box.size.y * oversample, 0); | |||
| DISTRHO_SAFE_ASSERT_RETURN(fb != nullptr,); | |||
| // draw our special framebuffer | |||
| nvgluBindFramebuffer(fb); | |||
| drawFramebufferForBrowserPreview(); | |||
| // reset to regular framebuffer | |||
| nvgluBindFramebuffer(args.fb); | |||
| // render image generated by our framebuffer | |||
| nvgBeginPath(args.vg); | |||
| nvgRect(args.vg, 0.0f, 0.0f, box.size.x, box.size.y); | |||
| NVGpaint paint = nvgImagePattern(args.vg, | |||
| 0.0f, 0.0f, box.size.x, box.size.y, | |||
| 0.0f, fb->image, 1.0f); | |||
| nvgFillPaint(args.vg, paint); | |||
| nvgFill(args.vg); | |||
| } | |||
| void onContextDestroy(const ContextDestroyEvent& e) override | |||
| { | |||
| deleteFramebuffer(); | |||
| OpenGlWidget::onContextDestroy(e); | |||
| } | |||
| void deleteFramebuffer() | |||
| { | |||
| if (fb == nullptr) | |||
| return; | |||
| nvgluDeleteFramebuffer(fb); | |||
| fb = nullptr; | |||
| } | |||
| virtual void drawFramebufferForBrowserPreview() = 0; | |||
| }; | |||
| #endif | |||
| extern Plugin* pluginInstance; | |||
| extern Model* modelAudioFile; | |||