Not logged in.  Login/Logout/Register | List snippets | | Create snippet | Upload image | Upload data

1648
LINES

< > BotCompany Repo | #1033862 // GazelleScreenCam [implementation of G22ProjectActions, Gazelle 22 main module]

JavaX source code (Dynamic Module) [tags: use-pretranspiled] - run with: Stefan's OS

Transpiled version (54188L) is out of date.

!7

set flag GazelleMain.

!include early #1035306 // "need latest" Include

// We're banning db_mainConcepts() again to allow multiple databases loaded at once

// !transpileMainSnippet #1033860

!include once #1033998 // RSyntaxTextArea
!include once #1013490 // sarxos webcam

!include early #1034876 // Gazelle 22 Flags [Include]

!include early #1033884 // Compact Module Include Gazelle V [dev version]

!include once #1034034 // Gazelle 22 Function Include for Scripts

!include once #1035461 // Gazelle 22 Class synonyms Include

module GazelleScreenCam is G22ProjectActions {
  !include early #1025212 // +Enabled without visualization
  { enabled = false; }

  transient static bool autoRunSelfTests = false;
  
  new SnPSettings screenCamSnPSettings;
  settableWithVar bool screenCamShowDecolorizerSelection;
  S script = "64p 8c gradientImage";
  settableWithVar S newScript;
  S screenCamScript;
  S selectedTab;
  S javaCode;
  bool horizontalLayout; // flat layout
  int fpsTarget = 20;
  S webCamName;
  bool webCamAccessEnabled = true;

  // libraries to load when project is opened
  gettable Set<S> projectLibs = syncSet();
  
  G22WatchTarget watchTarget;
  
  new G22_TestScreenPanel testScreen;

  // The following fields related to the screen cam panel
  transient ImageSurface isPosterized;
  transient new ScreenCamStream imageStream;

  transient Gazelle22_ImageToRegions imageToRegions_finished;
  transient new DoubleFPSCounter fpsCounter;
  transient int fps;
  transient WatchTargetSelector watchTargetSelector;
  transient new RollingAverage remainingMSPerFrame;
  transient int remainingMS;
  // End of fields for screen cam panel
  
  transient new FunctionTimings<S> functionTimings;
  
  transient ReliableSingleThread rstRunScript = dm_rst(me(), r _runScript);
  
  transient JGazelleVScriptRunner scriptRunner;
  
  new UIURLSystem uiURLs;

  //transient L<Webcam> availableWebCams;
  transient JComboBox<Webcam> cbWebCam;
  transient SingleComponentPanel scpWebCamImage;
  transient WebcamPanel webCamPanel;

  transient JLeftArrowScriptIDE screenCamScriptIDE;
  transient GazelleV_LeftArrowScript.Script runningScreenCamScript;
  
  // set by host
  transient gettable Concepts concepts;
  
  S uiURL = "Main Tabs";
  
  transient SimpleCRUD_v2<Label> labelCRUD;
  transient SimpleCRUD_v2<GalleryImage> galleryCRUD;
  transient JComponent galleryCRUDVis;

  transient SimpleCRUD_v2<Entity> entityCRUD;

  transient JGallery /*gallery,*/ paintToolGallery;
  //transient ImageSurface galleryImageSurface;

  transient FlexibleRateTimer screenCamTimer;
  transient SingleComponentPanel scpMain;
  transient gettable DynamicVStack topStack;
  transient gettable JTabbedPane mainTabs;
  transient bool showRegionsAsOutline = true;
  transient JComponent watchScreenPane;
  transient S screenCamRecognitionOutput;

  transient gettable new G22Utils g22utils;
  delegate stdImageSurface, setupScriptCRUD, dbDir, makeVariablesPanel to g22utils.

  delegate showUIURL, renderUIURL to uiURLs.
  
  transient new BackgroundProcessesUI backgroundProcessesUI;
  
  transient BackgroundProcessesUI.Entry bgScreenCam = backgroundProcessesUI.new Entry("Screen Cam")
    .menuItem(jMenuItem("Screen Cam", r showScreenCam));
  transient BackgroundProcessesUI.Entry bgWebCam = backgroundProcessesUI.new Entry("Web Cam")
    .menuItem(jMenuItem("Web Cam", r showWebCam));

  bool autoRunChallenge = true;

  transient G22ChallengesPanel challengesPanel;

  transient JComponent urlBar;

  transient JVideoLibDownloader videoLibDownloader;
  transient JFFMPEGVideoPlayer videoPlayer;

  // it's now actually persistent
  settableWithVar JPaintTool paintTool;

  // also persistent
  G22AnalysisPanel paintAnalysisPanel;
  
  // to detect when the project is moved. not implemented yet
  //settableWithVar File previousConceptsDir;

  transient gettable G22SelfTests selfTests;

  settableWithVar bool paintToolGalleryExpanded;
  
  transient JComponent screenCamTab;

  transient GazelleV_LeftArrowScriptParser prototypeParser;
  
  gettable transient JPanel generalInfoPanel = jline();
  
  //new Ref<G22Analyzer> screenCamAnalyzer; TODO: module is not a concept...
  settableWithVar G22Analyzer screenCamAnalyzer;
  
  settableWithVar G22LeftArrowScript showCasedScript;
  
  transient G22Analyzer.CompiledAnalyzer runningScreenCamAnalyzer;
  
  transient SourceTriggeredStream<BufferedImage> imageStreamForAnalyzer;
  
  transient SingleComponentPanel scpScreenCamImage = scp();
  
  settableWithVar bool imageRecognitionEnabled = true;
  
  gettable L<PersistableThrowable> projectErrors = syncLinkedList();
  transient settable int maxProjectErrors = 40;
  
  // add fields here

  start {
    g22utils.module(this);

    // load libraries if downloaded
    for (S libID : projectLibs) {
      File f = fileForLibrary(libID);
      if (fileExists(f)) {
        print("Loading iibrary " + libID);
        addLibrary(f);
      }
    }
    
    testScreen.g22utils(g22utils);
    testScreen.onChange(l0 change);
    
    dm_onFieldChange horizontalLayout(
      //r dm_revisualize // deh buggy
      r dm_reload
    );
    
    /*if (concepts == null) {
      // non-standalone mode (doesn't work yet)
      printWithMS("Starting DB");
      db();
      concepts = db_mainConcepts();
    } else
      assertSame(concepts, db_mainConcepts());*/
      
    concepts.quietSave = true;
    concepts.modifyOnCreate(true); // It's just the right thing to do.
    
    setModuleName(replaceIfEquals(g22utils.projectName(), g22utils.defaultDBName(), ""));

    // make indexes
    indexConceptField(concepts, GalleryImage, "path");
    indexConceptField(concepts, Example, "item");
    indexConceptFieldCI(concepts, G22Label, "name");
    indexConceptField(concepts, G22Variable, "name");
    indexConceptField(concepts, G22TravelLogEntry, "timestamp");
    ensureConceptClassesAreIndexed(concepts, G22PointOfInterest);
    
    // complete initialisation of g22utils
    g22utils.concepts(concepts);
    concepts.miscMapPut(G22Utils.class, g22utils);
    g22utils.backgroundProcessesUI(backgroundProcessesUI);
    g22utils.functionTimings(functionTimings);

    // Update project dir in G22ProjectInfo
    
    G22ProjectInfo projectInfo = g22utils.projectInfo();
    File oldProjectDir = projectInfo.projectDir;
    File projectDir = g22utils.projectDir();
    if (cset_trueIfChanged(projectInfo, +projectDir)
      && oldProjectDir != null) {
      projectInfo.addHistoricalProjectDir(oldProjectDir);
      //infoBox("TODO: Migrate project dir from " + oldProjectDir);
    }

    // Update travel log
    if (G22TravelLogEntry.shouldCreateEntry(g22utils))
      thread "Make Travel Log Entry" {
        printWithMS("Creating travel log entry.");
        registerConcept(concepts, G22TravelLogEntry.create(g22utils)
          .scanProject(g22utils)
          .action("First travel log entry"));
        printWithMS("Created travel log entry.");
      }

    // fix legacy bug
    for (c : list(concepts)) fixFieldValues(c);

    makePrototypeParser();

    g22utils.onSettingUpParser(l1 adaptLeftArrowParser);
    g22utils.projectActions(this);
    g22utils.onSettingUpScriptIDE(ide -> {
      // go-to-script handler (for "callScript 123")
      
      ide.addGoToDefinitionHandler(tokPtr -> {
        S prevToken = _get(tokPtr.list(), tokPtr.idx()-2);
        S token = tokPtr.get();
        if (eq(prevToken, "callScript") && isInteger(token)) {
          var script = g22utils.getScript(parseLong(token));
          if (script != null)
            ret -> g22utils.editScript(script);
        }
        null;
      });
      
      // project-defined functions + classes
      ide.addGoToDefinitionHandler(tokPtr -> {
        S token = tokPtr.get();
        if (!isIdentifier(token)) null;
        
        var def = findDefinition(token);
        ret def == null ?: -> goToSource(def);
      });
    
      if (!devMode()) ret;
      
      var scp = scp();
      addInFront(ide.buttons(), scp);
      
      ide.varCompileResult().onChange(cr -> {
        temp enter();

        // Offer to add standard function or class to Gazelle (if authorized)
        var e = innerExceptionOfType(GazelleV_LeftArrowScriptParser.UnknownObject.class, cr?.compileError);
        if (e != null) {
          //print("Checking repo for unknown object " + quote(e.name));
          // An unknown object is referenced in the source. Let's hunt for it.
          
          if (isStandardFunction(e.name)) {
            var btn = jbuttonWithDisable("Add function " + e.name, r {
              infoBoxAndCompile(addFunctionNameToInclude(functionsIncludeID(), e.name));
            });
            componentPopupMenuItem(btn, "Add but don't recompile",
              rThread {
                addFunctionNameToInclude(functionsIncludeID(), e.name);
                infoBox("Added");
              });
            ret with scp.set(btn);
          }
            
          if (isStandardClass(e.name))
            ret with scp.set(jbuttonWithDisable("Add class " + e.name, r {
              infoBoxAndCompile(addClassNameToInclude(functionsIncludeID(), e.name));
            }));
        }
 
        scp.clear();
      });
    });

    g22utils.autoStarter().init();

    //g22utils.basicParserTest();
    
    // update count in tab when tab isn't selected
    // TODO: move this to a separate helper
    /*onConceptChangeByClass(concepts, Label, -> {
      labelCRUD?.updateEnclosingTabTitle();
      //galleryCRUD?.updateEnclosingTabTitle();
    });*/
    
    uiURLs.owner = me();
    uiURLs.uiURL = dm_fieldLiveValue uiURL();
    uiURLs.addPreferredUIURL("Main Tabs");

    dm_watchFieldAndNow enabled(-> backgroundProcessesUI.addOrRemove(enabled, bgScreenCam));
    
    scriptRunner = new JGazelleVScriptRunner(dm_fieldLiveValue script(me()));
    
    printWithMS("Making image stream");

    imageStream.onNewElement(img -> {
      fpsCounter.inc();
      setField(fps := iround(fpsCounter!));

      // perform analysis on screen cam image

      // new G22_ImageAnalysis screenCamImageAnalysis;
      
      assertTrue("meta", getMetaSrc(img) instanceof ScreenShotMeta);
      
      if (screenCamAnalyzer != null) {
        imageStreamForAnalyzer?.newElement(img);
      } else {
        // DEFAULT analyzer (regions)
        Gazelle22_ImageToRegions itr = new(functionTimings, img, screenCamSnPSettings);
        itr.run();
        imageToRegions_finished = itr;
  
        // run "linear" script on image if any
        
        if (shouldRunScript()) rstRunScript.go();
  
        // run left-arrow script
  
        if (screenCamScriptIDE != null 
          && screenCamScriptIDE.visible()) try {
            g22_runPostAnalysisLeftArrowScript(itr, runningScreenCamScript);
          } catch e {
            printStackTrace(e);
            screenCamScriptIDE.showRuntimeError(e);
          }
  
        // make textual result of analysis
        
        S text = nRegions(itr.regions.regionCount());
        setField(screenCamRecognitionOutput := text);
        
        // display image after analysis so we can highlight a region
  
        g22_renderPosterizedHighlightedImage(isPosterized, itr, showRegionsAsOutline);
      } // end of DEFAULT analysis path
    });

    if (watchTarget == null) {
      watchTarget = defaultWatchTarget();
      change();
    }

    watchTargetSelector = new WatchTargetSelector;
    watchTargetSelector.visualize();

    /*if (webCamAccessEnabled) {
      printWithMS("Detecting web cams");
      watchTargetSelector.updateCamCount();
      printWithMS("Found " + n2(availableWebCams, "web cam"));
    }*/

    isPosterized = stdImageSurface();

    varScreenCamAnalyzer().onChangeAndNow(analyzer ->
      q().add(-> {
      if (analyzer == null) {
        runningScreenCamAnalyzer = null;
        imageStreamForAnalyzer = null;
        scpScreenCamImage.set(withTools(isPosterized));
      } else {
        runningScreenCamAnalyzer = analyzer.compileForAutoRun();
        imageStreamForAnalyzer = new SourceTriggeredStream<BufferedImage>;
        G22AnalysisContext context = new(g22utils, imageStreamForAnalyzer);
        O analyzerResult = runningScreenCamAnalyzer.get(context);
        print(+analyzerResult);
        scpScreenCamImage.set(new G22JavaObjectVisualizer(g22utils, analyzerResult));
      } // else branch
    }));

    printWithMS("Starting screen cam");
    
    ownResource(screenCamTimer = new FlexibleRateTimer(fpsTarget, rEnter {
      if (!enabled) ret;
      watchTargetSelector?.updateScreenCount();
      Timestamp deadline = tsNowPlusMS(1000/fpsTarget);
      watchTarget?.mainWindow(getWindow(urlBar));
      watchTarget?.configureScreenCamStream(imageStream);
      imageStream.step();
      long remaining = deadline.minus(tsNow());
      remainingMSPerFrame.add(remaining);
      setField(remainingMS := iround(remainingMSPerFrame!));
    }));
    screenCamTimer.start();
    dm_onFieldChange fpsTarget(-> screenCamTimer.setFrequencyImmediately(fpsTarget));

    printWithMS("Gathering images from disk");
    //addDirToGallery(galleryDir()));
    addDirToGallery(conceptsDir(concepts));
    printWithMS("Got dem images");

    transpileRaw_makeTranslator = -> hotwire(#7);

    // OSHI adds 4.5 MB to the jar...
    //doAfter(5.0, r { print("Process size: ", toM_str(oshi_currentProcessResidentSize_noZGCFix())) });
  } // end of start method

  void addDirToGallery(File dir) {
    watchDirForGallery(dir);
    for (f : allImageFiles(dir))
      addToGallery(f);
  }
  
  void watchDirForGallery(File dir) {
    g22utils.onProjectFileChanged(file -> {
      if (isImageFile(file))
        addToGallery(file);
    });
  }

  // main window visualization
  cachedVisualize {
    // if screen cam is not enabled, highlight regions of last captured image
    // on mouse hover
    
    imageSurfaceOnHover(isPosterized, pt -> {
      if (!enabled && imageToRegions_finished != null)
        g22_renderPosterizedHighlightedImage(isPosterized, imageToRegions_finished,
          showRegionsAsOutline);
    });
        
    labelCRUD = new SimpleCRUD_v2(concepts, Label);
    labelCRUD.hideFields("globalID");
    //labelCRUD.addCountToEnclosingTab(true);
    labelCRUD.itemToMap_inner2 = l
      -> litorderedmap(
        "Name" := l.name,
        "# Examples" := n2(countConcepts(concepts, Example, label := l)));
    
    galleryCRUD = new SimpleCRUD_v2(concepts, GalleryImage);
    //galleryCRUD.addCountToEnclosingTab(true);
    galleryCRUD.itemToMap_inner2 = img
      -> litorderedmap(
        "File" := fileName(img.path),
        "Folder" := dirPath(img.path));
    galleryCRUD.defaultAction(img -> { thread { showImage(img.path) }});
    galleryCRUDVis = galleryCRUD.visualize();
    galleryCRUD.addButton(jPopDownButton_noText(
      "Forget missing images" := rThreadEnter forgetMissingImages
    ));
    galleryCRUD.addButton("Back to gallery", rThreadEnter { showUIURL("Gallery") });
    galleryCRUD.newConcept = -> {
      new JFileChooser fc;
      fc.setDialogTitle("Add image to gallery");
      fc.setCurrentDirectory(conceptsDir(concepts));
      fc.setFileFilter(new ImageFileFilter().allowDirectories(true));
      addToGallery(execFileChooser(fc));
    };
    
    // main visual

    var screenCamSnPSelector = new G22SnPSelector(screenCamSnPSettings)
      .allowDecolorizerSelection(true);
    screenCamSnPSelector.onChange(l0 change);
    linkVars(varScreenCamShowDecolorizerSelection(),
      screenCamSnPSelector.varShowDecolorizerSelection());

    watchScreenPane = jHigherScrollPane_borderless_center (vstack(
      withLeftAndRightMargin(hstack(
        dm_rcheckBox enabled("Watch"),
        watchTargetSelector.visualize(),
        jlabel(" in "),
        screenCamSnPSelector,
        jlabel(" Analyzer "),
        bindComboBoxToVar(varScreenCamAnalyzer(),
          swing(-> new ConceptsComboBox(g22utils.concepts(), G22Analyzer)
            .allowNull(true))),
      )),
      verticalStrut(2),
      withSideMargins(centerAndEastWithMargin(
        dm_fieldLabel screenCamRecognitionOutput(),
        dm_transientCalculatedToolTip speedInfo_long(rightAlignLabel(dm_transientCalculatedLabel speedInfo()))
      )),
    ));

    topStack = dynamicVStack();
    topStack.spacing(0);

    initUIURLs();

    mainTabs = scrollingTabs(jTopOrLeftTabs(horizontalLayout));

    //addUIURLToMainTabs("Screen Cam");
    addUIURLToMainTabs("Project Overview");
    addUIURLToMainTabs("Showcase");
    addUIURLToMainTabs("Scripts");
    if (imageRecognitionEnabled) {
      addUIURLToMainTabs("Masks");
      addUIURLToMainTabs("Shape Collector");
      addUIURLToMainTabs("Points of interest");
    }
    addUIURLToMainTabs("Gallery");
    addUIURLToMainTabs("Scratchpad");
    addUIURLToMainTabs("Paint");
    if (imageRecognitionEnabled) {
      addUIURLToMainTabs("Analyzers");
      //addUIURLToMainTabs("Networks");
    }
    addUIURLToMainTabs("Projects");

    // add tabs here

    // add main tabs to UI URLs (probably don't need this anymore)
    for (S tab : tabNames(mainTabs)) {
      reMutable tab = dropTrailingBracketedCount(tab);
      uiURLs.put(tab, -> {
        int i = indexOfTabNameWithoutTrailingCount(mainTabs, tab);
        if (i < 0) ret jcenteredlabel("Hmm. Tab not found");
        selectTab(mainTabs, i);
        ret mainTabs;
      });
    }

    // Fix UI URL after tab was changed manually
    // Hopefully doesn't brake stuff (seems it doesn't)
    onTabSelected(mainTabs, -> {
      if (!isShowing(mainTabs)) ret;
      S tabName = dropTrailingBracketedCount(selectedTabName(mainTabs));
      if (!eqicOneOf(uiURL, "Main Tabs", tabName))
        uiURLs.showUIURL("Main Tabs");
        //uiURLs.showUIURL(tabName);
    });

    var cbEnabled = toolTip("Switch screen cam on or off", dm_checkBox enabled(""));
    var lblScreenCam = setToolTip("Show scaled down and color-reduced screen image",
      jlabel("Screen Cam"));
    tabComponentClickFixer(lblScreenCam);
    screenCamTab = hstackWithSpacing(cbEnabled, lblScreenCam);
    addActionListener(cbEnabled, -> { if (enabled) selectTabComponent(mainTabs, screenCamTab); });
    
    replaceTabTitleComponent(mainTabs, "Screen Cam", screenCamTab);
    
    // for tab titles
    /*challengesPanel?.updateCount();
    labelCRUD?.update();
    galleryCRUD?.update();
    for (crud : ll(analyzersPanel(), scriptsPanel(), poisPanel())) {
      crud.addCountToEnclosingTab(true);
      crud.updateCount();
    }*/
    
    persistSelectedTabAsLiveValue(mainTabs, dm_fieldLiveValue selectedTab());

    urlBar = uiURLs.urlBar();
    focusOnFirstShow(urlBar);
    setToolTip(uiURLs.comboBox, "UI navigation system");

    scpMain = singleComponentPanel();
    uiURLs.scp(scpMain);
    uiURLs.go();
    
    var projectSelector = new JG22ProjectSelector(g22utils);

    generalInfoPanel.add(backgroundProcessesUI.shortLabel());
    
    topStack.addComponent(verticalStrut(6));
    topStack.addComponent(
      westCenterAndEast(
        withLabelToTheRight(projectSelector.visualize(), "| "),
        urlBar,
        withLeftMargin(generalInfoPanel)));

    // MAIN LAYOUT
    
    var vis = northAndCenter(
      withSideMargin(topStack),
      scpMain
    );

    g22utils.autoStarter().start();

    if (autoRunSelfTests) {
      autoRunSelfTests = false;
      onFirstShow(vis, l0 runSelfTests);
    }

    ret vis;
  }

  void runSelfTests {
    thread "Self-Tests" {
      selfTests = new G22SelfTests(g22utils);
      selfTests.run();
      selfTestsDone();
    }
  }

  event selfTestsDone;
  
  event projectErrorsChanged;
  event projectErrorOccurred(PersistableThrowable error);

  JComponent screenCamPanel() {
    ret centerAndSouthOrEast(horizontalLayout,
      scpScreenCamImage,
      watchScreenPane);
  }
  
  JComponent screenCamPlusScriptPanel() enter {
    print("screenCamPlusScriptPanel");
    //ret watchScreenPane;
    try {
      var ide = screenCamScriptIDE = leftArrowScriptIDE();
      ide.runButtonShouldBeEnabled = ->
         eq(getText(ide.btnRun), "Stop")
          || ide.runButtonShouldBeEnabled_base();
      
      ide.runScript = -> {
        if (eq(getText(ide.btnRun), "Stop"))
          runningScreenCamScript = null;
        else {
          runningScreenCamScript = screenCamScriptIDE.parsedScript();
          ide.showStatus("Running");
        }
        setText(ide.btnRun,
          runningScreenCamScript == null ? "Run" : "Stop");
      };
      
      ret centerAndSouthOrEast(horizontalLayout,
        //withTools(
          jhsplit(
            jscroll_centered_borderless(isPosterized),
            screenCamScriptIDE
              .lvScript(dm_fieldLiveValue screenCamScript())
              .visualize())
        //, isPosterized)
        , watchScreenPane);
    } on fail e { print(e); }
  }
  
  S speedInfo() {
    ret "FPS " + fps + " idle " + remainingMS + " ms";
  }
  
  S speedInfo_long() {
    ret "Screen cam running at " + nFrames(fps) + "/second. " + n2(remainingMS) + " ms remaining per frame in first core"
      + " (of targeted " + fpsTarget + " FPS)";
  }
  
  bool useErrorHandling() { false; }
  
  S renderFunctionTimings() {
    ret lines(ciSorted(map(functionTimings!, (f, avg) ->
      firstToUpper(f) + ": " + n2(iround(nsToMicroseconds(avg!))) + " " + microSymbol() + "s (" + n2(iround(avg.n())) + ")")));
  }
  
  transient long _runScript_idx;

  void _runScript() {
    scriptRunner.parseAndRunOn(imageToRegions_finished.ii);
  }
  
  bool shouldRunScript() {
    ret isShowing(scriptRunner.scpScriptResult);
  }
  
  JComponent testScreenPanel() {
    ret withSideAndTopMargin(testScreen.visualize());
  }
  
  JComponent timingsPanel() {
    JTextArea taTimings = jTextArea_noUndo();
    awtEveryAndNow(taTimings, .5, r {
      setText(taTimings, renderFunctionTimings())
    });
    ret withRightAlignedButtons(taTimings,
      Reset := r resetTimings);
  }
  
  L popDownItems() {
    ret ll(jCheckBoxMenuItem_dyn("Horizontal Layout",
      -> horizontalLayout,
      b -> setField(horizontalLayout := b)));
  }
  
  void unvisualize {} // don't zero transient component fields
  
  void resetTimings { functionTimings.reset(); }
  
  // add tool side bar to image surface
  JComponent withTools(
    JComponent component default jscroll_centered_borderless(is),
    ImageSurface is) {
    ret centerAndEastWithMargin(component,
      vstack(
        verticalStrut(5),
        jimageButtonScaledToWidth(16, #1103054, "Save screenshot in gallery", rThread saveScreenshotToGallery),
        /*jPopDownButton_noText(
          jCheckBoxMenuItem_dyn("Live scripting", -> liveScripting, b -> setLiveScripting(b)
        ),*/
      )
    );
  }
  
  class WatchTargetSelector is Swingable {
    JComboBox<G22WatchTarget> cb = jComboBox();
    int screenCount, camCount;
    
    cachedVisualize {
      updateList();
      selectItem(cb, watchTarget);
      
      main onChangeAndNow(cb, watchTarget -> {
        setField(+watchTarget);
        toolTip(cb, watchTarget?.toolTip());
      });
      
      ret cb;
    }
    
    void updateScreenCount() {
      if (screenCount != screenCount())
        updateList();
    }

    void updateList() swing {
      setComboBoxItems(cb, makeWatchTargets());
    }
    
    L<G22WatchTarget> makeWatchTargets() {
      ret flattenToList(
        countIteratorAsList_incl(1, screenCount = screenCount(),
          i -> WatchScreen(i)),
        new WatchMouse,
        new WatchScreenWithMouse,
        new WatchOtherScreen,
      );
    }
  }

  JComponent wrapCRUD(S title, SimpleCRUD_v2 crud, JComponent vis default crud.visualize()) {
    ret crud == null ?: withTopAndBottomMargin(jCenteredRaisedSection(title, withMargin(vis)));
  }
  
  File galleryDir() {
    ret picturesDir(gazelle22_imagesSubDirName());
  }
  
  void saveScreenshotToGallery enter {
    var img = imageStream!;
    addToGallery(saveImageWithCounter(galleryDir(), "Screenshot", img));
  }

  GalleryImage addToGallery(S name default "Unnamed Image", BufferedImage image) {
    ret addToGallery(saveImageWithCounter(dbDir(), name, image));
  }
  
  void saveWebCamImageToGallery enter {
    var img = webCamPanel.getImage();
    addToGallery(saveImageWithCounter(galleryDir(), "Webcam", img));
  }
  
  GalleryImage addToGallery(File imgFile) {
    if (!isImageFile(imgFile)) null;
    var img = uniq(concepts, GalleryImage, path := imgFile);
    printVars("addToGallery", +imgFile, +img);
    ret img;
  }

  class StudyPanel {
    new G22SnPSelector snpSelector;
    GalleryImage image;
    BufferedImage originalImage;
    //ImageSurface isOriginal = stdImageSurface();
    ImageSurface isOriginalWithRegions = stdImageSurface();
    ImageSurface isPosterized = stdImageSurface();
    SingleComponentPanel scp = singleComponentPanel();
    SingleComponentPanel analysisPanel = singleComponentPanel();
    SingleComponentPanel scpExampleCRUD = singleComponentPanel();
    ConceptsComboBox<GalleryImage> cbImage = g22utils.galleryImagesComboBox();
    Gazelle22_ImageToRegions itr;
    int iSelectedRegion;
    SimpleCRUD_v2<SavedRegion> regionCRUD;
    SimpleCRUD_v2<Example> exampleCRUD;

    *() {
      print("Making StudyPanel " + this);
      //cbImage.sortTheList = l -> sortConceptsByIDDesc(l);
      cbImage.sortTheList = l -> sortedByComparator(l, (a, b)
        -> cmpAlphanumIC(fileName(a.path), fileName(b.path)));

      main onChangeAndNow(cbImage, img -> {
        /*print("Image selected 1: " + img);
        print("Me: " + me() + ", q thread: " + me().q().hasThread()
          + ", q jobs done: " + me().q().nJobsDone());
        print("Module queue current job: " + me().q().currentJob());*/
        dm_q(me(), r {
          print("Image selected: " + img);
          image = img;
          scp.setComponent(studyImagePanel());
        });
      });

      isPosterized.removeAllTools();
      isPosterized.onMousePositionChanged(r_dm_q(me(), l0 regionUpdate));

      imageSurfaceOnLeftMouseDown(isPosterized, pt -> dm_q(me(), r { chooseRegionAt(pt) }));

      snpSelector.onChange(r_dm_q(me(), l0 runSnP));
    }

    void chooseRegionAt(Pt p) {
      if (itr == null) ret;
      iSelectedRegion = itr.regions.regionAt(p);

      if (iSelectedRegion > 0) {
        var savedRegion = uniq_returnIfNew(concepts, SavedRegion,
          +image,
          snpSettings := snpSelector.settings.cloneMe(),
          regionIndex := iSelectedRegion);

        // it's new, add values
        if (savedRegion != null) {
          print("Saved new region!");
          var bitMatrix = toScanlineBitMatrix(itr.regions.regionBitMatrix(iSelectedRegion));
          cset(savedRegion, +bitMatrix);
        }
      }
      
      regionUpdate();
    }

    // load new image
    JComponent studyImagePanel() { 
      if (image == null) null;
      originalImage = loadImage2(image.path);
      if (originalImage == null) ret jcenteredlabel("Image not found");

      //isOriginal.setImage(originalImage);
      isOriginalWithRegions.setImage(originalImage);

      iSelectedRegion = 0;
      itr = new Gazelle22_ImageToRegions(functionTimings, originalImage, snpSelector.settings);
      runSnP();

      regionCRUD = new SimpleCRUD_v2<SavedRegion>(concepts, SavedRegion);
      regionCRUD.addFilter(+image);
      regionCRUD
        .showSearchBar(false)
        .showAddButton(false)
        .showEditButton(false)
        .iconButtons(true);
        
      regionCRUD.itemToMap_inner2 = region -> {
        var examples = conceptsWhere(concepts, Example, item := region);
        
        ret litorderedmap(
          "Pixels" := region.bitMatrix == null ? "-" : n2(region.bitMatrix.pixelCount()),
          //"Shape" := "TODO",
          "Labels" := joinWithComma(map(examples, e -> e.label)),

          // TODO: scale using snpsettings
          "Position" := region.bitMatrix == null ? "-" : region.bitMatrix.boundingBoxOfTrueBits());
      };

      var regionCRUDComponent = regionCRUD.visualize();
      regionCRUD.onSelectionChanged(l0 updateExampleCRUD);

      ret hsplit(
        jtabs(
          //"Image" := jscroll_centered_borderless(isOriginal),
          "Image + regions" := jscroll_centered_borderless(isOriginalWithRegions),
          "Posterized" := jscroll_centered_borderless(isPosterized),
        ),
        northAndCenterWithMargin(
          analysisPanel,
          hsplit(
            jCenteredSection("Saved regions", regionCRUDComponent),
            scpExampleCRUD)
          ));
    }

    void runSnP {
      if (itr == null) ret;
      itr.run();
      regionUpdate();
    }

    void regionUpdate {
      if (itr == null) ret;

      var pixels = itr.posterized.getRGBPixels();
      
      // hovering region marked green
      g22_highlightRegion(pixels, isPosterized, itr, showRegionsAsOutline);

      // selected region marked blue
      itr.regions.markRegionInPixelArray(pixels, iSelectedRegion, 0xFFADD8E6);

      var highlighted = bufferedImage(pixels, itr.posterized.getWidth(), itr.posterized.getHeight());

      isPosterized.setImage(highlighted);
      isPosterized.performAutoZoom(); // seems to be necessary for some reason

      updateAnalysis();
    }

    void updateAnalysis {
      /*new LS lines;
      lines.add(nRegions(itr.regions.regionCount()));
      lines.add("Selected region: " + iSelectedRegion);
      S text = lines_rtrim(lines);
      analysisPanel.setComponent(jLabel(text)));*/
    }

    void updateExampleCRUD {
      var region = regionCRUD.selected();
      if (region == null) ret with scpExampleCRUD.clear();
      
      exampleCRUD = new SimpleCRUD_v2<Example>(concepts, Example);
      exampleCRUD.entityName = -> "label for region";
      exampleCRUD.addFilter(item := region);
      exampleCRUD.showSearchBar(false);
      exampleCRUD.iconButtons(true);
      scpExampleCRUD.set(jCenteredSection("Labels for region",
        exampleCRUD.visualize()));
    }

    void importImage {
      new JFileChooser fc;
      if (fc.showOpenDialog(cbImage) == JFileChooser.APPROVE_OPTION) {
        File file = fc.getSelectedFile().getAbsoluteFile();
        if (!isImageFile(file)) ret with infoMessage("Not an image file");
        var img = addToGallery(file);
        waitUntil(250, 5.0, -> comboBoxContainsItem(cbImage, img));
        setSelectedItem(cbImage, img);
      }
    }

    visual
      jRaisedSection(northAndCenterWithMargins(
        centerAndEast(withLabel("Study", cbImage),
          hstack(
            jlabel(" in "),
            snpSelector.visualize(),
            horizontalStrut(10),
            jPopDownButton_noText(
              "Import image...", rThreadEnter importImage,
            ),
            )),
        scp));
  } // end of StudyPanel

  void initUIURLs {
    uiURLs.put("Main Tabs", ->
      horizontalLayout ? withMargin(mainTabs) : withSideMargin(mainTabs));
      
    uiURLs.put("Screen Cam FPS", -> jFullCenter(
      vstackWithSpacing(
        jCenteredLabel("FPS target (frames per second) for screen cam:"),
        jFullCenter(dm_spinner fpsTarget(1, 60)),
        jCenteredLabel("(You can lower this value if you have a slower computer)"))));

    uiURLs.put("Timings", -> timingsPanel());

    uiURLs.put("Test Screen" := -> testScreenPanel());

    //uiURLs.put("Operators" := -> operatorsPanel());

    uiURLs.put("Settings" := -> settingsPanel());

    uiURLs.put("System Info" := -> systemInfoPanel());

    uiURLs.put("Web Cam" := -> webCamPanel());

    uiURLs.put("Labels" := -> wrapCRUD("Labels", labelCRUD));

    uiURLs.put(WithToolTip("Screen Cam + Linear Script", "Run a simple (linear) Gazelle V script")
      := -> withBottomMargin(scriptRunner.scriptAndResultPanel()));
      
    uiURLs.put(WithToolTip("Scratchpad", "Write and run a \"left-arrow script\"")
      := -> withBottomMargin(jOnDemand scratchpad/*leftArrowScriptPanel*/()));
      
    uiURLs.put("Screen Cam + Script" := -> jOnDemand screenCamPlusScriptPanel());

    uiURLs.put(WithToolTip("Gallery CRUD", "Gallery view with more functions (delete etc)")
      := -> wrapCRUD("Images", galleryCRUD, galleryCRUDVis));

    uiURLs.put(WithToolTip("Paint", "Paint a picture for Gazelle with your mouse!")
      := -> withBottomMargin(jOnDemand paintPanel()));

    uiURLs.put(WithToolTip("Video", "A little video player (Note: no sound and doesn't handle some videos)")
      := -> withBottomMargin(videoPanel()));
    
    uiURLs.put(WithToolTip("Analyzers", "Manage image analyzers here")
      := -> withBottomMargin(analyzersPanel().visualize()));

    uiURLs.put(WithToolTip("Scripts", "Manage all left-arrow scripts here")
      := -> withBottomMargin(scriptsPanel().visualize()));
      
    uiURLs.put(WithToolTip("Shape Collector", "Looks at your screen live")
      := -> withBottomMargin(shapeCollectorPanel().visualize()));
      
    uiURLs.put(WithToolTip("Points of interest", "Points in random images you have clicked on")
      := -> withBottomMargin(poisPanel().visualize()));
      
    uiURLs.put(WithToolTip("Networks", "Manage recognition networks")
      := -> withBottomMargin(networksPanel().visualize()));
      
    uiURLs.put("Screen Cam" := -> jOnDemand screenCamPanel())

      .put(WithToolTip("Study", "Here you can analyze gallery images")
        := -> withTopAndBottomMargin(studyPanel().visualize()))
      
      .put(WithToolTip("Java", "Write & run actual Java code")
        := -> withBottomMargin(jOnDemand javaPanel()))
        
      .put(WithToolTip("Gallery", "Gallery view with preview images")
        := -> galleryPanel().visualize())
          
      .put(WithToolTip("Challenges", "Gazelle's self-tests")
        := -> challengesPanel())

      .put(WithToolTip("Projects", "Manage/load Gazelle projects")
        := -> databasesPanel().visualize())
        
      .put(WithToolTip("Entities", "Define entities (meta-level)")
        := -> entitiesPanel())

      .put("Local Fonts" := -> localFontsPanel().visualize())

      .put("Points of interest" := -> new G22POIsPanel().g22utils(g22utils).visualize())

      .put("Scripts from all projects" := -> new G22ScriptsFromAllDBsPanel(g22utils).visualize())

      .put("Networks from all projects" := -> new G22NetworksFromAllDBsPanel(g22utils).visualize())

      .put("Files in project" := -> new G22DBFileBrowser(g22utils).visualize())

      //.put("HTML Editor Test" := -> new G22HtmlEditor().visualize())

      .put("Project Overview" := -> new G22ProjectOverviewPanel(g22utils).visualize())

      .put("Showcase" := -> withTopMargin(new G22ShowCasePanel(g22utils).visualize()))

      .put("Story" := -> new G22ProjectStoryEditor(g22utils).visualize())

      .put("All objects by ID" := -> new G22AllConceptsPanel(g22utils).visualize())

      .put("Variables" := -> makeVariablesPanel().visualize())

      .put("Connectivity" := -> new G22ConnectivityPanel().g22utils(g22utils).visualize())

      .put("Travel Log" := -> new G22TravelLogPanel(g22utils).visualize())
      
      .put("Screen parts" := -> screenPartsPanel().g22utils(g22utils).visualize())

      .put("Masks" := -> masksPanel().g22utils(g22utils).visualize())
      
      // TODO: .put("Log" := -> log view)

      // add ui urls above this line
    ;
  }

  transient simplyCached G22LocalFontsPanel localFontsPanel() { ret new G22LocalFontsPanel(g22utils); }

  JComponent settingsPanel() {
    var cbMinimizeToTray = jCheckBox(isTrue(getOpt(dm_stem(), "minimizeToTray")));
    onUpdate(cbMinimizeToTray, -> dm_callStem(me(), "setMinimizeToTray", isChecked(cbMinimizeToTray)));
    ret jscroll_vertical(makeForm3( 
      "Minimize to tray", toolTip("Remove Gazelle from task bar when minimized (click Gazelle icon in system tray to reactivate)", cbMinimizeToTray),
      "Access web cams", dm_checkBox webCamAccessEnabled(),
    ));
  }

  JComponent operatorsPanel() {
    ret jcenteredlabel("TODO");
  }

  JLeftArrowScriptIDE leftArrowScriptIDE() {
    ret g22utils.leftArrowIDE();
  }

  void makePrototypeParser() {
    var parser = prototypeParser = new GazelleV_LeftArrowScriptParser;
    // global function containers for all scripts!
    parser.allowTheWorld(me(), new G22MaskUtils(g22utils), g22utils, mc(), utils.class);
    parser.isBlockedFunctionContainerMethod =
      (fc, name) -> fc == this && eqOneOf(name, "onChange", "onChangeAndNow", "removeChangeListener");
      
    //parser.importedClasses = -> listPlus(parser.importedClasses_base(),
      //"loadableUtils.utils$Timestamp");
      
    var classNames = parser.globalClassNames();
    classNames.put("Timestamp", "loadableUtils.utils$Timestamp");
  }

  void adaptLeftArrowParser(GazelleV_LeftArrowScriptParser parser) {
    parser.copyFunctionContainersFrom(prototypeParser);
  }

  JComponent scratchpad() {
    var scratchpad = uniq(concepts(), G22LAScratchpad);
    if (nempty(newScript)) {
      scratchpad.text(newScript);
      newScript(null);
    }
    ret scratchpad.visualize();
  }

  /*JComponent leftArrowScriptPanel() {
    var ide = leftArrowScriptIDE();

    var btnSave = jImageButton(#1103084, "Save script", rThreadEnter saveAsNewScript);
    
    // place to the right of hideable stuff
    int idx = 0;
    while (getComponentAtIndex(ide.buttons(), idx) instanceof SingleComponentPanel) idx++;
    addComponentAtIndex(ide.buttons(), idx, btnSave);
      
    ide.sectionTitle("Left Arrow Script Scratchpad");
    ide.lvScript(dm_fieldLiveValue newScript());
    ret withTopMargin(ide.visualize());
  }*/

  public void saveAsNewScript(S text /*default newScript*/) {
    inputText("Name for this script", description -> {
      temp enter();
      
      // Note: This only works like this, i.e. when showUIURL is called
      // first.
      
      showUIURL("Scripts");
      var script = cnewUnlisted(G22LeftArrowScript, +description, +text);
      scriptsPanel().selectAfterUpdate(script);
      concepts.register(script);
    });
  }

  JComponent javaPanel() enter {
    var scpResult = singleComponentPanel();
    
    new JMiniJavaIDE ide;
    ide.stringifier(g22utils.stringifier);
    ide.extraClassMembers = script -> {
      LS tok = javaTok(script);

      if (contains(tok, "draw")) ret [[
        import java.awt.*;
        import java.awt.image.*;
        
        interface SimpleRenderable {
          void renderOn(Graphics2D g);
        }
        
        static BufferedImage draw(int w, int h, SimpleRenderable r) {
          BufferedImage img = new BufferedImage(w, h, BufferedImage.TYPE_INT_RGB);
          Graphics2D g = img.createGraphics();
          g.setColor(Color.white);
          g.fillRect(0, 0, w, h);
          g.setColor(Color.black);
          r.renderOn(g);
          return img;
        }
      ]];
        
      ret "";
    };

    ide.callCompiledObject = o -> {
      O result = ide.callCompiledObject_base(o);
      if (result cast BufferedImage)
        scpResult.set(jscroll_centered_borderless(stdImageSurface(result)));
      ret result;
    };
    
    ide.lvScript(dm_fieldLiveValue javaCode());
    ret jhsplit(
      jCenteredSection("Image will show here", scpResult),
      ide.visualize());
  }

  JComponent webCamPanel() {
    if (cbWebCam == null) {
      var cams = listWebCams();
      cbWebCam = jTypedComboBox(cams, findWebCamByName(cams, webCamName));
    }

    if (scpWebCamImage == null) scpWebCamImage = singleComponentPanel();
    
    ret northAndCenterWithMargins(
      centerAndEastWithMargin(
        withLabel("Cam", cbWebCam),
        jline(
          jbutton("Start", rThreadEnter startWebCam),
          jbutton("Stop", rThreadEnter stopWebCam)
        )),
      scpWebCamImage);
  }

  void stopWebCam {
    if (webCamPanel != null) {
      webCamPanel.stop();
      webCamPanel = null;
      scpWebCamImage?.clear();
      backgroundProcessesUI.remove(bgWebCam);
    }
  }

  void startWebCam {
    temp tempInfoBox("Starting Web Cam");
    stopWebCam();
    var cam = getSelectedItem_typed(cbWebCam);
    setField(webCamName := cam?.getName());
    if (cam != null) {
      scpWebCamImage.set(
        northAndCenterWithMargin(
          rightAlignedLine(
            jimageButtonScaledToWidth(16, #1103054, "Save web cam image in gallery", rThread saveWebCamImageToGallery)
          ),
          webCamPanel = new WebcamPanel(cam, true)
      ));
      backgroundProcessesUI.add(bgWebCam);
    }
  }
  
  void forgetMissingImages {
    int n = l(deleteConcepts(concepts, GalleryImage, img -> !img.imageExists()));
    infoBox(n == 0 ? "Nothing to forget" : "Forgot " + nImages(n));
  }
  
  bool hasUIURL(S url) {
    ret uiURLs.hasURL(url);
  }

  void addUIURLToMainTabs(S url) swing {
    if (containsTabNameWithoutTrailingCount(mainTabs, url)) ret;
    if (!hasUIURL(url)) ret with print("URL not found: " + url);
    
    addTab(mainTabs, WithToolTip(uiURLs.toolTipForURL(url), url),
      uiURLs.renderUIURL(url));
  }

  void showScreenCam() { showUIURL("Screen Cam"); }
  void showWebCam() { showUIURL("Web Cam"); }
  
  transient simplyCached G22NetworksPanel networksPanel() {
    ret uniq(concepts, G22NetworksPanel).g22utils(g22utils);
  }

  JComponent challengesPanel() { 
    if (challengesPanel == null)
      challengesPanel = new G22ChallengesPanel().g22utils(g22utils);
    ret challengesPanel.visualize();
  }

  transient simplyCached G22GalleryPanel galleryPanel() {
    ret new G22GalleryPanel().g22utils(g22utils);
  }
  
  JComponent entitiesPanel() { 
    if (entityCRUD == null)
      entityCRUD = new SimpleCRUD_v2(concepts, Entity);
    ret wrapCRUD("Entities", entityCRUD);
  }
  
  // for scripts
  Gazelle22_ImageToRegions imageToRegions(BufferedImage inputImage, SnPSettings snpSettings,) {
    ret new Gazelle22_ImageToRegions(functionTimings, inputImage, snpSettings);
  }

  Gazelle22_ImageToRegions imageToRegionsWithDiagonals(BufferedImage inputImage, SnPSettings snpSettings) {
    ret imageToRegions(inputImage, snpSettings).withDiagonals(true);
  }
  
  G22DatabasesPanel databasesPanel() {
    ret new G22DatabasesPanel(g22utils);
  }

  transient simplyCached JComponent paintPanel() {
    if (paintToolGallery == null) {
      paintToolGallery = JGallery().horizontal(false);
      paintToolGallery.openImage = img -> paintTool.loadImageProtected(img);
      paintToolGallery.imageLoader().imageHeight(50);
      
      new AWTOnConceptChangesByClass(concepts, GalleryImage, paintToolGallery.visualize(), -> {
        var images = galleryImagesIn(conceptsDir(concepts));
        print("Paint tool images: " + l(images));
        paintToolGallery.setImageFiles(sortFilesAlphaNumIC(images));
      }).install();
    }
    
    if (paintTool == null) paintTool(new JPaintTool);
    ownResource(paintTool);
    main onChange(paintTool, me());

    paintTool.createAutoPersistFile =
      -> makeFileNameUnique_beforeExtension_startWith1_noDot(conceptsDir(concepts, "Painting.png"));
    
    new JG22Labels labelsView;
    paintTool.bottomLeftControls = -> labelsView.visualize();

    var paintToolVis = paintTool.visualize();

    paintTool.imageSurface().defaultImageDir = -> g22utils.dbDir();

    // migrating between directories - just make a new file
    if (!isDeepContainedInDir_canonical(paintTool.autoPersistFile(), conceptsDir(concepts)))
      paintTool.newImage();

    paintTool.varAutoPersistFile().onChangeAndNow(imageFile -> {
      labelsView.setLabels(g22utils.labelsForFile(imageFile));
      paintToolGallery.selectFile(imageFile);
    });
    labelsView.addLabel = text -> {
      var imageFile = paintTool.autoPersistFile();
      var labels = g22utils.labelsForFile(imageFile);
      labels.add(g22utils.getLabel(text));
      print(+labels);
      g22utils.setLabelsForFile(imageFile, labels);
      labelsView.setLabels(labels);
    };
    
    labelsView.removeLabel = label -> {
      var imageFile = paintTool.autoPersistFile();
      var labels = g22utils.labelsForFile(imageFile);
      labels = listWithout(labels, label);
      g22utils.setLabelsForFile(imageFile, labels);
      labelsView.setLabels(labels);
    };

    paintAnalysisPanel if null = new G22AnalysisPanel;
    paintAnalysisPanel.editAnalyzer = l1 editAnalyzer;
    paintAnalysisPanel.g22utils(g22utils).imageSurface(paintTool.imageSurface());
    ownResource(paintAnalysisPanel);
    main onChange(paintAnalysisPanel, me());

    paintTool.onImageChanged(img -> paintAnalysisPanel.setImage(img));
    paintAnalysisPanel.setImage(paintTool.getImage());

    var collapsiblePanel = CollapsibleLeftPanel(false, "Paintings",
      paintToolGallery.visualize(), paintToolVis);
    linkVars(varPaintToolGalleryExpanded(), collapsiblePanel.varExpanded());
    paintToolGallery.varFiles().onChangeAndNow(files -> collapsiblePanel.sidePanelName(n2(files, "Painting")));

    ret jvsplit(0.75,
      collapsiblePanel.visualize(),
      jRaisedCenteredSection("Image Analysis", paintAnalysisPanel.visualize()));
  }

  JComponent videoPanel() {
    if (videoLibDownloader == null) {
      videoLibDownloader = new JVideoLibDownloader();
      videoLibDownloader.forward(-> videoPlayer().visualize());
    }
    ret videoLibDownloader.visualize();
  }

  JFFMPEGVideoPlayer videoPlayer() {
    if (videoPlayer == null)
      videoPlayer = new JFFMPEGVideoPlayer;
    ret videoPlayer;
  }

  transient simplyCached G22AnalyzersPanel analyzersPanel() {
    ret uniq(concepts, G22AnalyzersPanel).g22utils(g22utils);
  }

  transient simplyCached G22ScriptsPanel scriptsPanel() {
    ret uniq(concepts, G22ScriptsPanel).g22utils(g22utils);
  }

  transient simplyCached G22ScreenPartsPanel screenPartsPanel() {
    ret uniq(concepts, G22ScreenPartsPanel).g22utils(g22utils);
  }

  transient simplyCached G22MasksPanel masksPanel() {
    ret uniq(concepts, G22MasksPanel).g22utils(g22utils);
  }

  transient simplyCached G22POIsPanel poisPanel() {
    ret uniq(concepts, G22POIsPanel).g22utils(g22utils);
  }

  // Not cached for now, stores defaults in project variables
  G22ShapeCollectorPanel shapeCollectorPanel() {
    ret G22ShapeCollectorPanel.fullyConfigured(g22utils);
  }

  Cl<File> galleryImagesIn(File dir) {
    ret mapNonNulls(list(concepts, GalleryImage),
      i -> isDeepContainedInDir_absolute(i.path, dir) ? i.path : null);
  }

  void editAnalyzer(G22Analyzer analyzer) {
    if (analyzer == null) ret;
    analyzersPanel().edit(analyzer);
    showUIURL("Analyzers");
  }

  public void editScript(G22LeftArrowScript script) {
    if (script == null) ret;
    showUIURL("Scripts");
    scriptsPanel().edit(script);
  }

  public void editScreenPart(G22ScreenPart screenPart) {
    screenPartsPanel().edit(screenPart);
    showUIURL("Screen parts");
  }

  O inlineScript(long scriptID, VarContext varContext) {
    var script = getConcept(concepts, G22LeftArrowScript, scriptID);
    if (script == null) fail("Script ID " + scriptID + " not found");
    GazelleV_LeftArrowScript.Script parsedScript = script.compileSaved().parsedScript;
    ret parsedScript.get(varContext);
  }

  transient simplyCached StudyPanel studyPanel() { ret new StudyPanel; }

  SingleComponentPanel jOnDemand(IF0<JComponent> makeComponent) {
    ret main jOnDemand(-> { temp enter(); ret makeComponent?!; });
  }

  // implementation of G22ProjectActions

  public void editScripts() {
    showUIURL("Scripts");
  }

  public void editProjectStory() {
    showUIURL("Story");
  }

  public void openObjectInProject(long id) {
    var c = getConcept(concepts, id);
    if (c == null)
      infoBox("Object ID not found in project: " + id);
    else
      openConcept(c);
  }

  public void openConcept(Concept c) {
    if (c cast G22Analyzer) {
      showUIURL("Analyzers");
      analyzersPanel().setSelected(c);
    } else if (c cast G22LeftArrowScript) {
      editScript(c);
    } else if (c cast G22PointOfInterest) {
      showUIURL("Points of interest");
      poisPanel().setSelected(c);
    } else if (c cast G22GalleryImage) {
      showInGallery(c);
    } else if (c cast G22Network) {
      showNetwork(c);
    } else
      infoBox("Don't know how to show " + c);
  }
  
  void showNetwork(G22Network network) {
    if (network == null) ret;
    showUIURL("Networks");
    networksPanel().setSelected(network);
  }
  
  public void openUIURL(S url) {
    uiURLs.showUIURL(url);
  }
  
  public bool openPathInProject(S path) {
    path = trim(path);
    S path2;
    
    if ((path2 = dropPrefixOrNull("../", path)) != null)
      ret true with masterStuff().openDB(path2, false);

    if (isInteger(path))
      ret true with openObjectInProject(parseLong(path));

    if (hasUIURL(path))
      ret true with openUIURL(path);

    File f = newFile(dbDir(), path);
    print(+f);
    if (isFile(f)) {
      if (isImageFile(f)) {
        showInGallery(addToGallery(f));
        true;
      } else {
        infoBox("Unknown file type: " + fileName(f));
        false;
      }
    }
    
    infoBox("Not a UI URL, object ID or project file: " + path);
    false;
  }

  void showInGallery(G22GalleryImage img) {
    if (img == null) ret;
    showUIURL("Gallery");
    galleryPanel().selectImage(img);
  }

  G22GalleryImage galleryImageConcept(long id) {
    ret getConcept(concepts, G22GalleryImage, id);
  }
  
  BufferedImage getGalleryImage(long id) {
    var img = galleryImageConcept(id);
    if (img == null) fail("Image not found: " + id);
    ret img.load();
  }

  void cleanMeUp_g22utils {
    g22utils.close();
  }

  public void waitUntilStarted aka waitForAutoStart() {
    g22utils.autoStarter().waitUntilDone();
  }

  S functionsIncludeID() { ret #1034034; }

  void infoBoxAndCompile(S msg) {
    //topLeftInfoBox(msg);
    infoBox(msg);
    if (cic(msg, "edited"))
      dm_callOS("compile");
  }

  bool devMode() { ret g22_devMode(); }

  G22WatchTarget defaultWatchTarget() {
    ret screenCount() > 1 ? new WatchOtherScreen : new WatchScreen(1);
  }

  G22MasterStuff masterStuff() { ret g22utils.masterStuff(); }

  void addProjectLibs(Cl<S> libs) { if (addAll(projectLibs, libs)) change(); }
  void removeProjectLibs(Cl<S> libs) { if (removeAll(projectLibs, libs)) change(); }
  
  void addUIURL(S url, IF0<? extends JComponent> maker) {
    uiURLs.put(url, maker);
  }

  L<IImageRegion<BWImage>> quickRegions(BufferedImage image, int colors) {
    ret imageToRegions(image, new SnPSettings(image, colors))!!;
  }
  
  L<IImageRegion<BWImage>> quickRegionsWithDiagonals(BufferedImage image, int colors) {
    ret imageToRegionsWithDiagonals(image, new SnPSettings(image, colors))!!;
  }
  
  WebcamListener webcamImageObtainedListener(IVF1<WebcamEvent> r) {
    ret new WebcamListener {
      public void webcamOpen(WebcamEvent we) {}
      public void webcamClosed(WebcamEvent we) {}
    
      public void webcamDisposed(WebcamEvent we) {}
    
      public void webcamImageObtained(WebcamEvent we) {
        r?.get(we);
      }
    };
  }
  
  void restartGazelle { masterStuff().restart(); }
  
  void closeProject {
    masterStuff().closeDatabase(g22utils.projectDir());
  }
  
  <A extends Throwable> A addProjectError(A e) {
    if (e == null) null;
    printStackTrace(e);
    var persistable = toPersistableThrowable(e);
    addToListWithMaxSize(projectErrors, persistable, maxProjectErrors);
    change();
    projectErrorOccurred(persistable);
    projectErrorsChanged();
    ret e;
  }
  
  void clearProjectErrors {
    if (syncClear_trueIfChanged(projectErrors)) {
      change();
      projectErrorsChanged();
    }
  }
  
  JComponent systemInfoPanel() {
    ret new G22SystemInfoPanel(g22utils).visualize();
  }
  
  public void goToSource(TokenRangeWithSrc src) {
    if (src == null) ret;
    var script = src.sourceInfo();
    if (script cast G22LeftArrowScript) {
      if (script._concepts() != concepts()) {
        // forward to other project
        main g22utils(script._concepts).projectActions().goToSource(src);
      } else {
        showUIURL("Scripts");
        scriptsPanel().edit(script, src.startLineAndCol());
      }
    }
  }

  TokenRangeWithSrc findDefinition(S id) {  
    var fd = g22utils.projectWideFunctionDefs.get(id);
    if (fd != null)
      ret fd.tokenRangeWithSrc();
      
    Class c = g22utils.projectWideClassDefs.get(id);
    if (c != null)
      ret (TokenRangeWithSrc) getOpt(c, "__srcRef");
      
    null;
  }
} // end of module

concept SavedRegion {
  new Ref image; // e.g. a GalleryImage
  SnPSettings snpSettings;
  int regionIndex; // region number relative to snpSettings
  //Rect bounds; // get it from bitMatrix instead
  ScanlineBitMatrix bitMatrix;
  //new RefL<Label> labels;
}

concept Example {
  new Ref<Label> label;
  new Ref item; // e.g. a SavedRegion
  double confidence = 1;
}

concept IfThenTheory {
  new Ref if_;
  new Ref then;
}

!include once #1034593 // Entity etc.

Author comment

Began life as a copy of #1033683

download  show line numbers  debug dex  old transpilations   

Travelled to 4 computer(s): bhatertpkbcr, mowyntqkapby, mqqgnosmbjvj, wnsclhtenguj

No comments. add comment

Snippet ID: #1033862
Snippet name: GazelleScreenCam [implementation of G22ProjectActions, Gazelle 22 main module]
Eternal ID of this version: #1033862/1113
Text MD5: 3b313a27c6e91b213243709959681b3f
Author: stefan
Category: javax / gazelle v
Type: JavaX source code (Dynamic Module)
Public (visible to everyone): Yes
Archived (hidden from active list): No
Created/modified: 2023-01-07 21:21:03
Source code size: 54431 bytes / 1648 lines
Pitched / IR pitched: No / No
Views / Downloads: 700 / 6351
Version history: 1112 change(s)
Referenced in: #1033860 - Gazelle 22 Standalone [dev version]
#1033924 - Gazelle Screen Cam / Gazelle 22 Module [backup before FlexibleRateTimer]
#1034012 - Gazelle Screen Cam / Gazelle 22 Module [backup]
#1034135 - Gazelle 22 Standalone [backup]
#1034151 - Gazelle Screen Cam / Gazelle 22 Module [backup]
#1034316 - Gazelle Screen Cam / Gazelle 22 Module [backup]
#1034390 - Gazelle 22 Standalone [dev version, backup]
#1035021 - Gazelle Screen Cam / Gazelle 22 Module [backup]