# CutBox.app

## Coverage Report

#### Created: 2024-03-12 03:40

    .../Source/App/CutBoxController.swift

    Line

    Count

    Source (jump to first uncovered line)

    1

    //

    2

    //  CutBoxController.swift

    3

    //  CutBox

    4

    //

    5

    //  Created by Jason Milkins on 17/3/18.

    6

    //  Copyright © 2018-2023 ocodo. All rights reserved.

    7

    //

    8

    9

    import Cocoa

    10

    import RxSwift

    11

    12

    typealias StatusItemDescriptor = (Int, String, String?, String?)

    13

    14

    class CutBoxController: NSObject {

    15

        @IBOutlet weak var statusMenu: CutBoxBaseMenu!

    16

    24

        var statusItem: NSStatusItem = cutBoxGetStatusItem()

    17

        var useCompactUI: CutBoxBaseMenuItem!

    18

        var hidePreview: CutBoxBaseMenuItem!

    19

        var searchModeSelectorsDict: [String: CutBoxBaseMenuItem]?

    20

        var searchViewController: SearchViewController

    21

        var jsFuncSearchViewController: JSFuncSearchViewController

    22

        var preferencesController: PreferencesTabViewController

    23

    24

        var aboutPanel: AboutPanel = AboutPanel.fromNib()!

    24

    24

        var hotKeyService = HotKeyService.shared

    25

    24

        var prefs = CutBoxPreferencesService.shared

    26

    24

        var historyService = HistoryService.shared

    27

    24

        var disposeBag = DisposeBag()

    28

    24

        var dialogFactory = DialogFactory()

    29

    24

        var nsAppProvider = CutBoxNSAppProvider()

    30

    31

    1

        @objc func searchClicked(_ sender: CutBoxBaseMenuItem) {

    32

    1

            self.searchViewController.togglePopup()

    33

    1

        }

    34

    35

    2

        @objc func clearHistoryClicked(_ sender: CutBoxBaseMenuItem?) {

    36

    2

            if dialogFactory.suppressibleConfirmationDialog(

    37

    2

                messageText: "confirm_warning_clear_history_title".l7n,

    38

    2

                informativeText: "confirm_warning_clear_history".l7n,

    39

    2

                dialogName: .clearHistoryWarning) {

    40

    1

                self.searchViewController.historyService.clear()

    41

    2

            }

    42

    2

        }

    43

    44

    1

        @objc func openPreferences(_ sender: CutBoxBaseMenuItem) {

    45

    1

            NSApplication.shared.activate(ignoringOtherApps: true)

    46

    1

            preferencesController.open()

    47

    1

        }

    48

    49

    1

        @objc func openAboutPanel(_ sender: CutBoxBaseMenuItem) {

    50

    1

            aboutPanel.makeKeyAndOrderFront(self)

    51

    1

            aboutPanel.center()

    52

    1

        }

    53

    54

    1

        @objc func quitClicked(_ sender: CutBoxBaseMenuItem) {

    55

    1

            nsAppProvider.terminate(sender)

    56

    1

        }

    57

    58

    2

        @objc func useCompactUIClicked(_ sender: CutBoxBaseMenuItem) {

    59

    2

            self.prefs.useCompactUI.toggle()

    60

    2

        }

    61

    62

    2

        @objc func hidePreviewClicked(_ sender: CutBoxBaseMenuItem) {

    63

    2

            self.prefs.hidePreview.toggle()

    64

    2

        }

    65

    66

    1

        @objc func searchModeSelect(_ sender: CutBoxBaseMenuItem) {

    67

    1

            searchModeSelectAxID(sender.accessibilityIdentifier())

    68

    1

        }

    69

    70

    1

        @objc func reloadJavascript(_ sender: CutBoxBaseMenuItem) {

    71

    1

            self.prefs.loadJavascript()

    72

    1

        }

    73

    74

    1

        @objc func reloadThemes(_ sender: CutBoxBaseMenuItem) {

    75

    1

            self.prefs.loadThemes()

    76

    1

        }

    77

    78

    24

        override init() {

    79

    24

            self.searchViewController = SearchViewController()

    80

    24

            self.jsFuncSearchViewController = JSFuncSearchViewController()

    81

    24

            self.preferencesController = PreferencesTabViewController()

    82

    24

            super.init()

    83

    24

        }

    84

    85

    0

        override func awakeFromNib() {

    86

    0

            self.setup()

    87

    0

        }

    88

    89

    1

        func setup() {

    90

    1

            self.hotKeyService.configure()

    91

    1

            self.setMenuItems()

    92

    1

            self.setupEventBindings()

    93

    1

        }

    94

    }

    95

    96

    extension CutBoxController {

    97

    5

        var statusMenuItems: [StatusItemDescriptor] {

    98

    5

            [

    99

    5

                (0, "cutbox_menu_search_cutbox".l7n, nil, "searchClicked:"),

    100

    5

                (1, "---", nil, nil),

    101

    5

                (2, "cutbox_menu_fuzzy_match".l7n, "fuzzyMatch", "searchModeSelect:"),

    102

    5

                (3, "cutbox_menu_regexp_any_case_match".l7n, "regexpAnyCase", "searchModeSelect:"),

    103

    5

                (4, "cutbox_menu_regexp_case_match".l7n, "regexpStrictCase", "searchModeSelect:"),

    104

    5

                (5, "cutbox_menu_substring_match".l7n, "substringMatch", "searchModeSelect:"),

    105

    5

                (6, "---", nil, nil),

    106

    5

                (7, "cutbox_menu_compactui".l7n, nil, "useCompactUIClicked:"),

    107

    5

                (8, "cutbox_menu_hide_preview".l7n, nil, "hidePreviewClicked:"),

    108

    5

                (9, "---", nil, nil),

    109

    5

                (10, "cutbox_menu_preferences".l7n, nil, "openPreferences:"),

    110

    5

                (11, "cutbox_menu_clear_history".l7n, nil, "clearHistoryClicked:"),

    111

    5

                (12, "---", nil, nil),

    112

    5

                (13, "preferences_javascript_transform_reload".l7n, nil, "reloadJavascript:"),

    113

    5

                (14, "preferences_color_theme_reload_themes".l7n, nil, "reloadThemes:"),

    114

    5

                (15, "---", nil, nil),

    115

    5

    116

    5

                // 16: Sparkle: Check for Updates

    117

    5

                //     It will find and fill the empty slot automatically.

    118

    5

    119

    5

                (17, "cutbox_menu_about".l7n, nil, "openAboutPanel:"),

    120

    5

                (18, "cutbox_menu_quit".l7n, nil, "quitClicked:")

    121

    5

                //     So number all the indexes correctly, leaving a gap.

    122

    5

            ]

    123

    5

        }

    124

    125

    5

        func setMenuItems() {

    126

    5

            let menu = self.statusMenu!

    127

    5

            self.statusItem.menu = menu

    128

    5

    129

    5

            let icon = CutBoxImageRef.statusIcon.image(with: "")

    130

    5

            icon.isTemplate = true // best for dark mode

    131

    5

            self.statusItem.button?.image = icon

    132

    90

            self.statusMenuItems.forEach { addMenuItems(menu: menu, descriptor: $0) }

    133

    5

            self.statusItem.menu = menu

    134

    5

    135

    5

            setModeSelectors(menu.items)

    136

    5

    137

    5

            self.useCompactUI = menu.item(at: 7)!

    138

    5

            self.hidePreview = menu.item(at: 8)!

    139

    5

    140

    5

            setCompactUIMenuItem()

    141

    5

            setHidePreviewMenuItem()

    142

    5

        }

    143

    144

    90

        func addMenuItems(menu: CutBoxBaseMenu, descriptor: StatusItemDescriptor) {

    145

    90

            let title = descriptor.1

    146

    90

            let index = descriptor.0

    147

    90

    148

    90

            if title == "---" {

    149

    25

                menu.insertItem(CutBoxBaseMenuItem.separator(), at: index)

    150

    90

            } else {

    151

    65

                let axID = descriptor.2

    152

    65

                let action = Selector(descriptor.3!)

    153

    65

    154

    65

                let item: CutBoxBaseMenuItem = CutBoxBaseMenuItem(

    155

    65

                    title: title,

    156

    65

                    action: action,

    157

    65

                    keyEquivalent: "")

    158

    65

    159

    65

                item.target = self

    160

    65

                if axID != nil {

    161

    20

                    item.setAccessibilityIdentifier(axID)

    162

    65

                }

    163

    65

                menu.insertItem(item, at: index)

    164

    90

            }

    165

    90

        }

    166

    }

    167

    168

    extension CutBoxController {

    169

    2

        func setupEventBindings() {

    170

    2

            self.setHotKeyServiceEventBindings()

    171

    2

            self.setSearchEventBindings()

    172

    2

            self.setPreferencesEventBindings()

    173

    2

        }

    174

    175

    2

        func setHotKeyServiceEventBindings() {

    176

    2

            self.hotKeyService.events

    177

    2

                .subscribe(onNext: onNext)

    $s15CutBoxUnitTests0aB10ControllerC29setHotKeyServiceEventBindingsyyFyAA0gH6EventsOcACcfu_

    Line

    Count

    Source

    177

    2

                .subscribe(onNext: onNext)

    Unexecuted instantiation: $s15CutBoxUnitTests0aB10ControllerC29setHotKeyServiceEventBindingsyyFyAA0gH6EventsOcACcfu_yAFcfu0_

    178

    2

                .disposed(by: disposeBag)

    179

    2

        }

    180

    181

    2

        func setSearchEventBindings() {

    182

    2

            self.searchViewController.events

    183

    2

                .subscribe(onNext: onNext)

    $s15CutBoxUnitTests0aB10ControllerC22setSearchEventBindingsyyFyAA0G10ViewEventsOcACcfu_

    Line

    Count

    Source

    183

    2

                .subscribe(onNext: onNext)

    Unexecuted instantiation: $s15CutBoxUnitTests0aB10ControllerC22setSearchEventBindingsyyFyAA0G10ViewEventsOcACcfu_yAFcfu0_

    184

    2

                .disposed(by: disposeBag)

    185

    2

        }

    186

    187

    2

        func setPreferencesEventBindings() {

    188

    2

            self.prefs.events

    189

    2

                .subscribe(onNext: onNext)

    $s15CutBoxUnitTests0aB10ControllerC27setPreferencesEventBindingsyyFyAA0abgH0OcACcfu_

    Line

    Count

    Source

    189

    2

                .subscribe(onNext: onNext)

    Unexecuted instantiation: $s15CutBoxUnitTests0aB10ControllerC27setPreferencesEventBindingsyyFyAA0abgH0OcACcfu_yAFcfu0_

    190

    2

                .disposed(by: disposeBag)

    191

    2

        }

    192

    }

    193

    194

    extension CutBoxController {

    195

    1

        func onNext(event: HotKeyEvents) {

    196

    1

            switch event {

    197

    1

            case .search:

    198

    1

                self.searchViewController.togglePopup()

    199

    1

            }

    200

    1

        }

    201

    202

    5

        func onNext(event: SearchViewEvents) {

    203

    5

            switch event {

    204

    5

            case .openPreferences:

    205

    1

                self.preferencesController.open()

    206

    5

            case .toggleSearchMode:

    207

    1

                self.checkSearchModeItem()

    208

    5

            case .setSearchMode(let mode):

    209

    1

                self.checkSearchModeItem(mode.rawValue)

    210

    5

            case .clearHistory:

    211

    1

                self.clearHistoryClicked(nil)

    212

    5

            case .selectJavascriptFunction:

    213

    1

                if  self.jsFuncSearchViewController.hasFuncs {

    214

    0

                    self.openJavascriptPopup()

    215

    5

                }

    216

    5

            default:

    217

    0

                break

    218

    5

            }

    219

    5

        }

    220

    221

    4

        func onNext(event: CutBoxPreferencesEvent) {

    222

    4

            switch event {

    223

    4

            case .javascriptReloaded:

    224

    0

                break

    225

    4

            case .historyLimitChanged(let limit):

    226

    1

                self.historyService.historyLimit = limit

    227

    4

            case .compactUISettingChanged(let isOn):

    228

    1

                self.useCompactUI.state = isOn ? .on : .off

    229

    4

            case .hidePreviewSettingChanged(let isOn):

    230

    1

                self.hidePreview.state = isOn ? .on : .off

    231

    4

            case .historyClearByOffset(let offset):

    232

    1

                self.clearHistoryByTimeOffset(offset: offset)

    233

    4

            default:

    234

    0

                break

    235

    4

            }

    236

    4

        }

    237

    }

    238

    239

    extension CutBoxController {

    240

    1

        func clearHistoryByTimeOffset(offset: TimeInterval) {

    241

    1

            self.historyService.clearHistoryByTimeOffset(offset: offset)

    242

    1

        }

    243

    }

    244

    245

    extension CutBoxController {

    246

    1

        func openJavascriptPopup() {

    247

    1

            let items = self.searchViewController.selectedClips

    248

    1

            self.jsFuncSearchViewController.selectedClips = items

    249

    1

    250

    1

            self.jsFuncSearchViewController

    251

    1

                .jsFuncView

    252

    1

                .applyTheme()

    253

    1

    254

    1

            self.jsFuncSearchViewController

    255

    1

                .jsFuncPopup

    256

    1

                .togglePopup()

    257

    1

        }

    258

    }

    259

    260

    extension CutBoxController {

    261

    5

        func setCompactUIMenuItem() {

    262

    5

            self.useCompactUI.title = "preferences_use_compact_ui".l7n

    263

    5

            self.useCompactUI.state = self.prefs.useCompactUI ? .on : .off

    264

    5

        }

    265

    266

    5

        func setHidePreviewMenuItem() {

    267

    5

            self.hidePreview.title = "preferences_hide_preview".l7n

    268

    5

            self.hidePreview.state = self.prefs.hidePreview ? .on : .off

    269

    5

        }

    270

    }

    271

    272

    extension CutBoxController {

    273

    5

        func setModeSelectors(_ items: [NSMenuItem]) {

    274

    5

            self.searchModeSelectorsDict = Dictionary(

    275

    5

                uniqueKeysWithValues: HistorySearchMode

    276

    20

                    .allCases.map { $0.rawValue }

    277

    20

                    .map { ($0, items.find(axID: $0)) })

    278

    5

    279

    5

            checkSearchModeItem(

    280

    5

                HistoryService

    281

    5

                    .shared.searchMode.rawValue

    282

    5

            )

    283

    5

        }

    284

    285

    1

        func searchModeSelectAxID(_ axID: String) {

    286

    1

            let mode = HistorySearchMode.searchMode(from: axID)

    287

    1

            self.searchViewController.events.onNext(.setSearchMode(mode))

    288

    1

        }

    289

    290

    2

        func checkSearchModeItem() {

    291

    2

            let rawValue = historyService.searchMode.rawValue

    292

    8

            self.searchModeSelectorsDict?.values.forEach { $0.state = .off }

    293

    2

            self.searchModeSelectorsDict?[rawValue]?.state = .on

    294

    2

        }

    295

    296

    6

        func checkSearchModeItem(_ axID: String) {

    297

    24

            self.searchModeSelectorsDict?.values.forEach { $0.state = .off }

    298

    6

            self.searchModeSelectorsDict?[axID]?.state = .on

    299

    6

        }

    300

    }