File: InfoBar.mm

package info (click to toggle)
codequery 1.0.1%2Bdfsg-1
  • links: PTS, VCS
  • area: main
  • in suites: forky, sid
  • size: 17,860 kB
  • sloc: cpp: 151,420; xml: 16,576; python: 5,602; ansic: 5,487; makefile: 559; perl: 496; ruby: 209; sql: 194; sh: 106; php: 53; vhdl: 51; erlang: 47; objc: 22; lisp: 18; cobol: 18; modula3: 17; asm: 14; fortran: 12; ml: 11; tcl: 6
file content (410 lines) | stat: -rw-r--r-- 14,053 bytes parent folder | download | duplicates (2)
1
2
3
4
5
6
7
8
9
10
11
12
13
14
15
16
17
18
19
20
21
22
23
24
25
26
27
28
29
30
31
32
33
34
35
36
37
38
39
40
41
42
43
44
45
46
47
48
49
50
51
52
53
54
55
56
57
58
59
60
61
62
63
64
65
66
67
68
69
70
71
72
73
74
75
76
77
78
79
80
81
82
83
84
85
86
87
88
89
90
91
92
93
94
95
96
97
98
99
100
101
102
103
104
105
106
107
108
109
110
111
112
113
114
115
116
117
118
119
120
121
122
123
124
125
126
127
128
129
130
131
132
133
134
135
136
137
138
139
140
141
142
143
144
145
146
147
148
149
150
151
152
153
154
155
156
157
158
159
160
161
162
163
164
165
166
167
168
169
170
171
172
173
174
175
176
177
178
179
180
181
182
183
184
185
186
187
188
189
190
191
192
193
194
195
196
197
198
199
200
201
202
203
204
205
206
207
208
209
210
211
212
213
214
215
216
217
218
219
220
221
222
223
224
225
226
227
228
229
230
231
232
233
234
235
236
237
238
239
240
241
242
243
244
245
246
247
248
249
250
251
252
253
254
255
256
257
258
259
260
261
262
263
264
265
266
267
268
269
270
271
272
273
274
275
276
277
278
279
280
281
282
283
284
285
286
287
288
289
290
291
292
293
294
295
296
297
298
299
300
301
302
303
304
305
306
307
308
309
310
311
312
313
314
315
316
317
318
319
320
321
322
323
324
325
326
327
328
329
330
331
332
333
334
335
336
337
338
339
340
341
342
343
344
345
346
347
348
349
350
351
352
353
354
355
356
357
358
359
360
361
362
363
364
365
366
367
368
369
370
371
372
373
374
375
376
377
378
379
380
381
382
383
384
385
386
387
388
389
390
391
392
393
394
395
396
397
398
399
400
401
402
403
404
405
406
407
408
409
410

/**
 * Scintilla source code edit control
 * @file InfoBar.mm - Implements special info bar with zoom info, caret position etc. to be used with
 *              ScintillaView.
 *
 * Mike Lischke <mlischke@sun.com>
 *
 * Copyright 2009 Sun Microsystems, Inc. All rights reserved.
 * This file is dual licensed under LGPL v2.1 and the Scintilla license (http://www.scintilla.org/License.txt).
 */

#include <cmath>

#import "InfoBar.h"

//--------------------------------------------------------------------------------------------------

@implementation VerticallyCenteredTextFieldCell

// Inspired by code from Daniel Jalkut, Red Sweater Software.

- (NSRect) drawingRectForBounds: (NSRect) theRect {
	// Get the parent's idea of where we should draw
	NSRect newRect = [super drawingRectForBounds: theRect];

	// When the text field is being edited or selected, we have to turn off the magic because it
	// screws up the configuration of the field editor. We sneak around this by intercepting
	// selectWithFrame and editWithFrame and sneaking a reduced, centered rect in at the last minute.
	if (mIsEditingOrSelecting == NO) {
		// Get our ideal size for current text
		NSSize textSize = [self cellSizeForBounds: theRect];

		// Center that in the proposed rect
		CGFloat heightDelta = newRect.size.height - textSize.height;
		if (heightDelta > 0) {
			newRect.size.height -= heightDelta;
			newRect.origin.y += std::ceil(heightDelta / 2);
		}
	}

	return newRect;
}

//--------------------------------------------------------------------------------------------------

- (void) selectWithFrame: (NSRect) aRect inView: (NSView *) controlView editor: (NSText *) textObj
		delegate: (id) anObject start: (NSInteger) selStart length: (NSInteger) selLength {
	aRect = [self drawingRectForBounds: aRect];
	mIsEditingOrSelecting = YES;
	[super selectWithFrame: aRect
			inView: controlView
			editor: textObj
		      delegate: anObject
			 start: selStart
			length: selLength];
	mIsEditingOrSelecting = NO;
}

//--------------------------------------------------------------------------------------------------

- (void) editWithFrame: (NSRect) aRect inView: (NSView *) controlView editor: (NSText *) textObj
	      delegate: (id) anObject event: (NSEvent *) theEvent {
	aRect = [self drawingRectForBounds: aRect];
	mIsEditingOrSelecting = YES;
	[super editWithFrame: aRect
		      inView: controlView
		      editor: textObj
		    delegate: anObject
		       event: theEvent];
	mIsEditingOrSelecting = NO;
}

@end

//--------------------------------------------------------------------------------------------------

@implementation InfoBar

- (instancetype) initWithFrame: (NSRect) frame {
	self = [super initWithFrame: frame];
	if (self) {
		NSBundle *bundle = [NSBundle bundleForClass: [InfoBar class]];

		NSString *path = [bundle pathForResource: @"info_bar_bg" ofType: @"tiff" inDirectory: nil];
		// macOS 10.13 introduced bug where pathForResource: fails on SMB share
		if (path == nil) {
			path = [bundle.bundlePath stringByAppendingPathComponent: @"Resources/info_bar_bg.tiff"];
		}
		mBackground = [[NSImage alloc] initWithContentsOfFile: path];
		if (!mBackground.valid)
			NSLog(@"Background image for info bar is invalid.");

		mScaleFactor = 1.0;
		mCurrentCaretX = 0;
		mCurrentCaretY = 0;
		[self createItems];
		self.clipsToBounds = TRUE;
	}
	return self;
}

//--------------------------------------------------------------------------------------------------

/**
 * Called by a connected component (usually the info bar) if something changed there.
 *
 * @param type The type of the notification.
 * @param message Carries the new status message if the type is a status message change.
 * @param location Carries the new location (e.g. caret) if the type is a caret change or similar type.
 * @param value Carries the new zoom value if the type is a zoom change.
 */
- (void) notify: (NotificationType) type message: (NSString *) message location: (NSPoint) location
	  value: (float) value {
	switch (type) {
	case IBNZoomChanged:
		[self setScaleFactor: value adjustPopup: YES];
		break;
	case IBNCaretChanged:
		[self setCaretPosition: location];
		break;
	case IBNStatusChanged:
		mStatusTextLabel.stringValue = message;
		break;
	}
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to set a protocol object we can use to send change notifications to.
 */
- (void) setCallback: (id <InfoBarCommunicator>) callback {
	mCallback = callback;
}

//--------------------------------------------------------------------------------------------------

static NSString *DefaultScaleMenuLabels[] = {
	@"20%", @"30%", @"50%", @"75%", @"100%", @"130%", @"160%", @"200%", @"250%", @"300%"
};
static float DefaultScaleMenuFactors[] = {
	0.2f, 0.3f, 0.5f, 0.75f, 1.0f, 1.3f, 1.6f, 2.0f, 2.5f, 3.0f
};
static unsigned DefaultScaleMenuSelectedItemIndex = 4;
static float BarFontSize = 10.0;

- (void) createItems {
	// 1) The zoom popup.
	unsigned numberOfDefaultItems = sizeof(DefaultScaleMenuLabels) / sizeof(NSString *);

	// Create the popup button.
	mZoomPopup = [[NSPopUpButton alloc] initWithFrame: NSMakeRect(0.0, 0.0, 1.0, 1.0) pullsDown: NO];

	// No border or background please.
	[mZoomPopup.cell setBordered: NO];
	[mZoomPopup.cell setArrowPosition: NSPopUpArrowAtBottom];

	// Fill it.
	for (unsigned count = 0; count < numberOfDefaultItems; count++) {
		[mZoomPopup addItemWithTitle: NSLocalizedStringFromTable(DefaultScaleMenuLabels[count], @"ZoomValues", nil)];
		id currentItem = [mZoomPopup itemAtIndex: count];
		if (DefaultScaleMenuFactors[count] != 0.0)
			[currentItem setRepresentedObject: @(DefaultScaleMenuFactors[count])];
	}
	[mZoomPopup selectItemAtIndex: DefaultScaleMenuSelectedItemIndex];

	// Hook it up.
	mZoomPopup.target = self;
	mZoomPopup.action = @selector(zoomItemAction:);

	// Set a suitable font.
	mZoomPopup.font = [NSFont menuBarFontOfSize: BarFontSize];

	// Make sure the popup is big enough to fit the cells.
	[mZoomPopup sizeToFit];

	// Don't let it become first responder
	[mZoomPopup setRefusesFirstResponder: YES];

	// put it in the scrollview.
	[self addSubview: mZoomPopup];

	// 2) The caret position label.
	Class oldCellClass = [NSTextField cellClass];
	[NSTextField setCellClass: [VerticallyCenteredTextFieldCell class]];

	mCaretPositionLabel = [[NSTextField alloc] initWithFrame: NSMakeRect(0.0, 0.0, 50.0, 1.0)];
	[mCaretPositionLabel setBezeled: NO];
	[mCaretPositionLabel setBordered: NO];
	[mCaretPositionLabel setEditable: NO];
	[mCaretPositionLabel setSelectable: NO];
	[mCaretPositionLabel setDrawsBackground: NO];
	mCaretPositionLabel.font = [NSFont menuBarFontOfSize: BarFontSize];

	NSTextFieldCell *cell = mCaretPositionLabel.cell;
	cell.placeholderString = @"0:0";
	cell.alignment = NSTextAlignmentCenter;

	[self addSubview: mCaretPositionLabel];

	// 3) The status text.
	mStatusTextLabel = [[NSTextField alloc] initWithFrame: NSMakeRect(0.0, 0.0, 1.0, 1.0)];
	[mStatusTextLabel setBezeled: NO];
	[mStatusTextLabel setBordered: NO];
	[mStatusTextLabel setEditable: NO];
	[mStatusTextLabel setSelectable: NO];
	[mStatusTextLabel setDrawsBackground: NO];
	mStatusTextLabel.font = [NSFont menuBarFontOfSize: BarFontSize];

	cell = mStatusTextLabel.cell;
	cell.placeholderString = @"";

	[self addSubview: mStatusTextLabel];

	// Restore original cell class so that everything else doesn't get broken
	[NSTextField setCellClass: oldCellClass];
}

//--------------------------------------------------------------------------------------------------


//--------------------------------------------------------------------------------------------------

/**
 * Fill the background.
 */
- (void) drawRect: (NSRect) rect {
	[[NSColor controlBackgroundColor] set];
	[NSBezierPath fillRect: rect];

	// Since the background is seamless, we don't need to take care for the proper offset.
	// Simply tile the background over the invalid rectangle.
	if (mBackground.size.width != 0) {
		NSPoint target = {rect.origin.x, 0};
		while (target.x < rect.origin.x + rect.size.width) {
			[mBackground drawAtPoint: target fromRect: NSZeroRect operation: NSCompositingOperationSourceOver fraction: 1];
			target.x += mBackground.size.width;
		}
	}

	// Draw separator lines between items.
	NSRect verticalLineRect;
	CGFloat component = 190.0 / 255.0;
	NSColor *lineColor = [NSColor colorWithDeviceRed: component green: component blue: component alpha: 1];

	if (mDisplayMask & IBShowZoom) {
		verticalLineRect = mZoomPopup.frame;
		verticalLineRect.origin.x += verticalLineRect.size.width + 1.0;
		verticalLineRect.size.width = 1.0;
		if (NSIntersectsRect(rect, verticalLineRect)) {
			[lineColor set];
			NSRectFill(verticalLineRect);
		}
	}

	if (mDisplayMask & IBShowCaretPosition) {
		verticalLineRect = mCaretPositionLabel.frame;
		verticalLineRect.origin.x += verticalLineRect.size.width + 1.0;
		verticalLineRect.size.width = 1.0;
		if (NSIntersectsRect(rect, verticalLineRect)) {
			[lineColor set];
			NSRectFill(verticalLineRect);
		}
	}
}

//--------------------------------------------------------------------------------------------------

- (BOOL) isOpaque {
	return YES;
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to reposition our content depending on the size of the view.
 */
- (void) setFrame: (NSRect) newFrame {
	super.frame = newFrame;
	[self positionSubViews];
}

//--------------------------------------------------------------------------------------------------

- (void) positionSubViews {
	NSRect currentBounds = {{0, 0}, {0, self.frame.size.height}};
	if (mDisplayMask & IBShowZoom) {
		[mZoomPopup setHidden: NO];
		currentBounds.size.width = mZoomPopup.frame.size.width;
		mZoomPopup.frame = currentBounds;
		currentBounds.origin.x += currentBounds.size.width + 1; // Add 1 for the separator.
	} else
		[mZoomPopup setHidden: YES];

	if (mDisplayMask & IBShowCaretPosition) {
		[mCaretPositionLabel setHidden: NO];
		currentBounds.size.width = mCaretPositionLabel.frame.size.width;
		mCaretPositionLabel.frame = currentBounds;
		currentBounds.origin.x += currentBounds.size.width + 1;
	} else
		[mCaretPositionLabel setHidden: YES];

	if (mDisplayMask & IBShowStatusText) {
		// The status text always takes the rest of the available space.
		[mStatusTextLabel setHidden: NO];
		currentBounds.size.width = self.frame.size.width - currentBounds.origin.x;
		mStatusTextLabel.frame = currentBounds;
	} else
		[mStatusTextLabel setHidden: YES];
}

//--------------------------------------------------------------------------------------------------

/**
 * Used to switch the visible parts of the info bar.
 *
 * @param display Bitwise ORed IBDisplay values which determine what to show on the bar.
 */
- (void) setDisplay: (IBDisplay) display {
	if (mDisplayMask != display) {
		mDisplayMask = display;
		[self positionSubViews];
		self.needsDisplay = YES;
	}
}

//--------------------------------------------------------------------------------------------------

/**
 * Handler for selection changes in the zoom menu.
 */
- (void) zoomItemAction: (id) sender {
	NSNumber *selectedFactorObject = [[sender selectedCell] representedObject];

	if (selectedFactorObject == nil) {
		NSLog(@"Scale popup action: setting arbitrary zoom factors is not yet supported.");
		return;
	} else {
		[self setScaleFactor: selectedFactorObject.floatValue adjustPopup: NO];
	}
}

//--------------------------------------------------------------------------------------------------

- (void) setScaleFactor: (float) newScaleFactor adjustPopup: (BOOL) flag {
	if (mScaleFactor != newScaleFactor) {
		mScaleFactor = newScaleFactor;
		if (flag) {
			unsigned count = 0;
			unsigned numberOfDefaultItems = sizeof(DefaultScaleMenuFactors) / sizeof(float);

			// We only work with some preset zoom values. If the given value does not correspond
			// to one then show no selection.
			while (count < numberOfDefaultItems && (std::abs(newScaleFactor - DefaultScaleMenuFactors[count]) > 0.07))
				count++;
			if (count == numberOfDefaultItems)
				[mZoomPopup selectItemAtIndex: -1];
			else {
				[mZoomPopup selectItemAtIndex: count];

				// Set scale factor to found preset value if it comes close.
				mScaleFactor = DefaultScaleMenuFactors[count];
			}
		} else {
			// Internally set. Notify owner.
			[mCallback notify: IBNZoomChanged message: nil location: NSZeroPoint value: newScaleFactor];
		}
	}
}

//--------------------------------------------------------------------------------------------------

/**
 * Called from the notification method to update the caret position display.
 */
- (void) setCaretPosition: (NSPoint) position {
	// Make the position one-based.
	int newX = (int) position.x + 1;
	int newY = (int) position.y + 1;

	if (mCurrentCaretX != newX || mCurrentCaretY != newY) {
		mCurrentCaretX = newX;
		mCurrentCaretY = newY;

		mCaretPositionLabel.stringValue = [NSString stringWithFormat: @"%d:%d", newX, newY];
	}
}

//--------------------------------------------------------------------------------------------------

/**
 * Makes the bar resize to the smallest width that can accommodate the currently enabled items.
 */
- (void) sizeToFit {
	NSRect frame = self.frame;
	frame.size.width = 0;
	if (mDisplayMask & IBShowZoom)
		frame.size.width += mZoomPopup.frame.size.width;

	if (mDisplayMask & IBShowCaretPosition)
		frame.size.width += mCaretPositionLabel.frame.size.width;

	if (mDisplayMask & IBShowStatusText)
		frame.size.width += mStatusTextLabel.frame.size.width;

	self.frame = frame;
}

@end